diff --git a/apps/client/src/features/page/tree/components/doc-tree.tsx b/apps/client/src/features/page/tree/components/doc-tree.tsx index 69d88fe2..d93b9d15 100644 --- a/apps/client/src/features/page/tree/components/doc-tree.tsx +++ b/apps/client/src/features/page/tree/components/doc-tree.tsx @@ -16,6 +16,11 @@ import { treeModel } from '../model/tree-model'; import { DocTreeRow } from './doc-tree-row'; import styles from '../styles/tree.module.css'; +// Page-tree row heights. STANDARD is the safe default density; COMPACT is the +// denser layout gated behind the COMPACT_PAGE_TREE feature flag. +export const ROW_HEIGHT_STANDARD = 32; +export const ROW_HEIGHT_COMPACT = 26; + export type RenderRowProps = { node: TreeNode; level: number; @@ -122,11 +127,11 @@ function DocTreeInner( selectedId, renderRow, indentPerLevel = 8, - // Compact vertical density: each virtualized row occupies exactly this - // many px (the virtualizer stride). Row content is ~22px (18px icon / - // 14px text / 20px action icons), so 26px keeps a small, even gap between - // nodes without clipping. Lower => denser tree. - rowHeight = 26, + // Each virtualized row occupies exactly this many px (the virtualizer + // stride). Default is standard density (32px); the denser compact layout + // (26px) is opt-in and driven by the COMPACT_PAGE_TREE feature flag in + // consumers. Lower => denser tree. + rowHeight = ROW_HEIGHT_STANDARD, onMove, onToggle, onSelect, diff --git a/apps/client/src/features/page/tree/components/space-tree.tsx b/apps/client/src/features/page/tree/components/space-tree.tsx index 1c3aab8e..e4fd1810 100644 --- a/apps/client/src/features/page/tree/components/space-tree.tsx +++ b/apps/client/src/features/page/tree/components/space-tree.tsx @@ -22,7 +22,12 @@ import { treeModel } from "@/features/page/tree/model/tree-model"; import { getPageBreadcrumbs } from "@/features/page/services/page-service.ts"; import { IPage } from "@/features/page/types/page.types.ts"; import { extractPageSlugId } from "@/lib"; -import { DocTree } from "./doc-tree"; +import { isCompactPageTreeEnabled } from "@/lib/config.ts"; +import { + DocTree, + ROW_HEIGHT_COMPACT, + ROW_HEIGHT_STANDARD, +} from "./doc-tree"; import { SpaceTreeRow } from "./space-tree-row"; interface SpaceTreeProps { @@ -33,6 +38,7 @@ interface SpaceTreeProps { export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) { const { t } = useTranslation(); const { pageSlug } = useParams(); + const compactTree = isCompactPageTreeEnabled(); const [data, setData] = useAtom(treeDataAtom); const { handleMove } = useTreeMutation(spaceId); const { @@ -219,6 +225,7 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) { renderRow={renderRow} onMove={handleMove} onToggle={handleToggle} + rowHeight={compactTree ? ROW_HEIGHT_COMPACT : ROW_HEIGHT_STANDARD} readOnly={readOnly} disableDrag={disableDragDrop} disableDrop={disableDragDrop} diff --git a/apps/client/src/features/share/components/shared-tree.tsx b/apps/client/src/features/share/components/shared-tree.tsx index 370c59e7..59915a57 100644 --- a/apps/client/src/features/share/components/shared-tree.tsx +++ b/apps/client/src/features/share/components/shared-tree.tsx @@ -25,7 +25,10 @@ import { DocTree, type DocTreeApi, type RenderRowProps, + ROW_HEIGHT_COMPACT, + ROW_HEIGHT_STANDARD, } from "@/features/page/tree/components/doc-tree"; +import { isCompactPageTreeEnabled } from "@/lib/config.ts"; import { openSharedTreeNodesAtom } from "@/features/share/atoms/open-shared-tree-nodes-atom"; interface SharedTreeProps { @@ -36,6 +39,7 @@ export default function SharedTree({ sharedPageTree }: SharedTreeProps) { const { t } = useTranslation(); const treeRef = useRef(null); const { pageSlug } = useParams(); + const compactTree = isCompactPageTreeEnabled(); const [openTreeNodes, setOpenTreeNodes] = useAtom(openSharedTreeNodesAtom); const currentNodeId = extractPageSlugId(pageSlug); @@ -100,6 +104,7 @@ export default function SharedTree({ sharedPageTree }: SharedTreeProps) { renderRow={SharedTreeRow} onMove={noopMove} onToggle={handleToggle} + rowHeight={compactTree ? ROW_HEIGHT_COMPACT : ROW_HEIGHT_STANDARD} getDragLabel={getDragLabel} aria-label={t("Pages")} /> diff --git a/apps/client/src/lib/config.ts b/apps/client/src/lib/config.ts index bae1a1c6..adc5615d 100644 --- a/apps/client/src/lib/config.ts +++ b/apps/client/src/lib/config.ts @@ -43,6 +43,10 @@ export function isCloud(): boolean { return castToBoolean(getConfigValue("CLOUD")); } +export function isCompactPageTreeEnabled(): boolean { + return castToBoolean(getConfigValue("COMPACT_PAGE_TREE", "true")); +} + export function getAvatarUrl( avatarUrl: string, type: AvatarIconType = AvatarIconType.AVATAR, diff --git a/apps/server/src/integrations/environment/environment.service.ts b/apps/server/src/integrations/environment/environment.service.ts index aa5fc554..6bbc6dba 100644 --- a/apps/server/src/integrations/environment/environment.service.ts +++ b/apps/server/src/integrations/environment/environment.service.ts @@ -214,6 +214,13 @@ export class EnvironmentService { return !this.isCloud(); } + isCompactPageTreeEnabled(): boolean { + const compactTree = this.configService + .get('COMPACT_PAGE_TREE', 'true') + .toLowerCase(); + return compactTree === 'true'; + } + getStripePublishableKey(): string { return this.configService.get('STRIPE_PUBLISHABLE_KEY'); } diff --git a/apps/server/src/integrations/static/static.module.ts b/apps/server/src/integrations/static/static.module.ts index f0b7e831..b7565d7f 100644 --- a/apps/server/src/integrations/static/static.module.ts +++ b/apps/server/src/integrations/static/static.module.ts @@ -35,6 +35,7 @@ export class StaticModule implements OnModuleInit { ENV: this.environmentService.getNodeEnv(), APP_URL: this.environmentService.getAppUrl(), CLOUD: this.environmentService.isCloud(), + COMPACT_PAGE_TREE: this.environmentService.isCompactPageTreeEnabled(), FILE_UPLOAD_SIZE_LIMIT: this.environmentService.getFileUploadSizeLimit(), FILE_IMPORT_SIZE_LIMIT: