Make the Content sidebar easier to scan - #5719
Conversation
Bring the Content sidebar in line with the Clips navigation grammar:
- One filled active row style shared by Recent, Pinned, and Files, with
hover lighter than active; the open page scrolls into view in Files
unless it is already visible elsewhere in the sidebar.
- One row type scale (14px, full foreground), fixed 16px icon slot for
emoji and glyphs, and row-height Show more/less aligned to the icon
column.
- Wider section spacing, Trash moved into a fixed lifecycle group below
the scroller, and depth guides under each ancestor's icon column with
a consistent 2px row rhythm at every depth.
- Row actions fade the title end instead of re-truncating it; section
menus appear on hover or focus; Settings lives only in the footer.
- New page is a visible Files header action, and workspace sources moved
into the space switcher; search is a quiet row.
- Tree toggles are named after the item ("Expand Research") instead of
"Expand sidebar Research", with translations for every locale.
Visual recap — generation failedThe visual recap could not be generated for this pull request. This is informational only and does not block the PR. Diagnostic: No plan URL: Repair changed too much of targeted file plan.mdx; expected a localized parser fix. Agent output: Repaired the malformed wireframe |
Pull the hover/focus row-actions layer (overlay, "..." menu, title fade, pin item) out of the Files row into SidebarRowActions so every sidebar section uses one implementation. Recent rows now use it with personal-only actions: Pin/Unpin and Remove from Recent. Shared mutations (delete, add child, reorder) stay on Files. - get-content-recent returns the requester's pinned state for rows that already resolved under access. - New remove-content-recent action forgets one Recent destination for the current user and context; agents can use it too. - Recent updates optimistically for both pin and remove. - Parity matrix, capability record, and all locale catalogs updated.
Files, Pinned, and Recent now render the same SidebarPageMenu in one order: pin; copy link and open in a new tab; rename, duplicate, and move; remove from Recent or move to Trash; then who last edited the Page. Each section passes only the actions it allows, so Recent keeps to actions that leave the Page unchanged. - Rename edits the title inline and starts after the menu closes, so the menu's focus trap cannot commit the input early. - Duplicate keeps the copy beside its original and in the Page's own space Files membership, even when the Page is pinned. - Move to opens a same-space picker over move-document. - The footer reads the new get-document-activity action while open. - Reorderable rows stay inside the sidebar width, so Pinned rows show their actions again.
Move to now picks a Content space first. Moving a Page into another space moves its sub-pages too: the mover becomes the owner, access resets to the destination's (organization-wide, private, or the new parent's sharing), existing shares and public access are removed, and comments, history, and block identities travel with the Pages. The dialog warns and asks before a cross-space move. - move-document takes spaceId and runs the move in one transaction, swapping Files membership and rewriting owner-scoped rows. Pages with collections, collection rows, local-folder files, or Notion/Builder links are refused with a reason. - New duplicate-page action copies a Page and its sub-pages, beside the original or into another space; the sidebar's Duplicate uses it. - The Move picker now opens on the Page's own space, fixing moves of Pinned and Recent pages from other spaces.
…cher Take main's workspace menu (sources inside the space switcher) and its + menu for a new Page or Collection, and drop this branch's Files header + along with the split WorkspaceSourceMenu it relied on.
- Duplicating a collection row keeps the collection page as its parent again; only Files pages keep their own parent. Top-level position shifts stay within the source space and root section. - duplicate-page requires edit access and refuses, instead of silently trimming, a subtree with sub-pages the caller can't read. - Move search leaves out the page's own subtree via a new search-documents excludeSubtreeOf filter. - The row menu reads last-edit activity only while it is open. - Recent copies in-app links for local-file pages, reloads pin state when a pin fails, and a failed removal restores only that entry.
- Format with oxfmt (the repo's formatter), including locale files. - Point the pin-glyph layout test at the shared SidebarRowActions menu. - duplicate-page: no silent catches, reject collection pages up front, require a parent in the destination space, copy extra Blocks field content within a space, and report pages copied from their last save. - Cross-space move refuses pages with content in the old Files table's extra Blocks fields and aborts if a sub-page appears mid-move.
There was a problem hiding this comment.
Builder reviewed your changes and found 1 potential issue 🟡
Review Details
Incremental Code Review Summary
The latest commits fix four previously reported issues, and those stale threads were resolved: duplicate destination-parent validation, secondary Blocks-field copying during duplication, live-editor flush status reporting, and collection-backed duplicate handling. The concurrent subtree-move race and cross-space Blocks-field preservation comments remain unresolved and were not reposted.
New Finding
🟡 MEDIUM — duplicate-page still omits description from every createDocument payload. Since createDocument defaults the field to empty, duplicating a Page silently loses its stored description even though the action advertises duplicating the Page and its sub-pages.
Risk assessment: High because the PR contains document duplication and cross-space hierarchy mutations; this review's new finding is medium severity.
🧪 Browser testing: Will run after this review (PR touches UI code)
There was a problem hiding this comment.
Builder reviewed your changes and found 5 potential issues 🟡
Review Details
Incremental Code Review Summary
The existing duplicate-page description-preservation comment remains unresolved and was not reposted. The latest changes improve duplicate destination validation and add more subtree consistency checks, but several edge cases remain around all-or-nothing duplication and concurrent cross-space moves.
New Findings
🟡 MEDIUM — Failed duplication uses soft-delete cleanup, and that cleanup can also require stricter access than the editor who started the copy. A later failure can therefore leave a visible partial subtree in Trash or leave shared-editor copies behind.
🟡 MEDIUM — Cross-space move checks still race with child creation and reparenting: the source tree is snapshotted before the relevant source locks, and already-loaded descendants are not checked for changed parent relationships.
🟡 MEDIUM — Cross-space duplication can silently drop secondary Blocks fields, omit nested collection pages and their descendants, and permit a local-folder/source-backed destination even though those Pages are disk-backed and should not receive SQL-managed copies.
Risk assessment: High because this PR mutates document hierarchy, ownership, and user-authored content across spaces.
🧪 Browser testing: Will run after this review (PR touches UI code)
Keep Trash from highlighting the current page in Pinned and Recent.
…sidebar-legibility
What changed
A legibility pass on the Content sidebar, using Clips' navigation as the reference, plus one shared row-actions layer for every section.
Legibility (first commit)
…menus appear on hover or focus, and always show on devices without hover. Settings appears once, in the footer. Search is a quiet row.main, the header keeps its space switcher (which holds workspace sources) and the+beside it for a new Page or Collection (feat(content): clarify page and collection creation #5592). This branch's earlier Files-header+was removed.Shared row actions and a personal Recent menu (second commit)
…menu, title fade, stay-open-while-menu-open, pin item) moved out of the Files row intoSidebarRowActions. Files and Pinned rows render through it unchanged.get-content-recentnow returns the requester's pinned state, looked up only for rows that already resolved under access. A newremove-content-recentaction forgets one destination for the current user and context. It's available to the agent, and pages, pins and other users' Recent are untouched.matrix.mdregenerated. Thecontent.navigation.sidebarcapability record now states the personal-actions promise and has a "Forget a Recent destination" acceptance story. New strings are translated in every locale.One Page menu with rename, duplicate, and move (third commit)
…again. Reorderable rows were wider than the sidebar and clipped their actions. They now stay inside its width.SidebarPageMenu, grouped Notion-style: Pin | Copy link, Open in new tab | Rename, Duplicate, Move to | Remove from Recent or Move to Trash | "Last edited by … · when". Each section passes only what it allows. The menu is 240px wide to fit the footer.duplicate-database-itemnow preserves the parent and, when given a Page id, prefers the Page's own space Files membership over a Favorites membership, so duplicating a pinned Page no longer puts a stray copy in Pinned. There's a db test for nested, root and pinned sources.move-document, which still rejects moving a Page under its own descendants.get-document-activityaction (viewer access). It is read only while the menu is open.Move and duplicate between spaces (fourth commit)
Product decisions: the person moving becomes the owner, sharing changes to match the destination (with a warning), and sub-pages move with the page and are copied on Duplicate.
move-documentacceptsspaceId. It walks the page's whole subtree regardless of owner, including trashed sub-pages that would restore under it. You need edit access to every page in it. In one transaction it:move-document's existing same-owner rule.duplicate-pageaction. It copies a page and its sub-pages beside the original, or into another space. The sidebar's Duplicate now uses it. Each copy is created throughcreate-document, so placement, sharing and Files membership follow the normal creation rules. Collections inside the page aren't copied. Property values carry over within the same space. Open editors are flushed first when they respond.Verification
tsc --noEmitfor Content passes.move-page-to-space(new, 6),move-document(6),space-aware-writers(6),content-files(17),move-database-item(5).database-row-batch-actionssource check assume LF line endings, and pass once the files are converted to LF.shared/__nfm_probe.test.tswrites to/tmp, which doesn't exist on Windows.guard:content-product-docsvalidates all records with LF line endings; in the CRLF checkout it fails on every record.guard:no-default-chrome,guard:content-product-conformance,guard:no-action-twin-routes,guard:help-icon-scale,guard:modal-layer-integrityandguard:i18n-changed-copypass.🤖 Generated with Claude Code