Library View Modes — Design Spec
Milestone: library-view-modes (GitHub #312) Manual: TBD — the user manual needs a “Changing how the Library looks” section: icon/list/ table/columns as the everyday browsing modes, Sheet/Cards/Timeline/Calendar/Map as the dataset modes over the same rows, Canvas/Space as the spatial modes, and that switching between them never changes what you’re looking AT, only how it’s drawn.
Design-led spec (Testing Constitution). The creative director owns the intent; tests enforce it; code makes them pass. One line per behavior, each cited by its pinning test. Status: DRAFT — PASS 1 of 2 (built behaviors only, grounded in disk 2026-09-18; the fold-in of the nine legacy milestones’ ~101 open issues is PASS 2, awaiting the fold plan’s review before any issue moves). Tags: [OK] behaves this way today · [PARTIAL] implemented, not fully verified/tested · [BROKEN] regression, code contradicts the line · [GAP] intended, never built.
Intent (the design)
This spec is the OTHER axis from modes-to-panes.md. That spec rules that everything the
sidebar can select — a workflow, a chat, an entity, the knowledge graph — is a node, not
a mode, and that selecting one never changes what the Library pane IS: “the Library pane is
always the navigator… selecting a node keeps the Library showing the tree/table it already
showed.” This spec covers what happens ONCE a folder or a KG collection IS the thing the
Library is navigating: the same row set (documents, or — per that spec’s own
m2p.kg-graph-retires-as-library-takeover — entities/claims when a KG collection is selected)
renders through one of eleven interchangeable view modes: Icon, List, Table, Columns, four
dataset renderers (Sheet, Cards, Timeline, Calendar, Map) plus Space, and Canvas. Changing the
view mode is a lens change, never a content change — the same node set, the same
selection, the same chrome (bottom action bar, row context menu, drag/drop, ⌘A) hold across
every mode. This is the Finder-like direct-manipulation principle already invoked elsewhere in
these specs (kg-tables.md, sidebar-crud.md): show ALL items, multi-select works everywhere,
one set of gestures.
Nine legacy GitHub milestones (UX - Library & Reading Surface, Library View, Library View —
Icons/List/Spatial/Canvas/Column Browser & Columns, Library - Engine, UX - Representations)
held roughly 101 open issues about exactly this surface with no spec behind any of them — this
spec is that surface’s home, per the legacy-milestones ledger
(agent-work/spec-pipeline/legacy-milestones-ledger.md, row 1 of the burn-down).
Surfaces: ViewDisplayMode (App/ViewDisplayMode.swift), LibraryView
+ContentBranches.swift (libraryRowsOrEmptyState, the one dispatch switch),
LibraryView+CanvasModes.swift, DatasetModeView/DatasetRenderer
(Views/Library/ViewModes/Dataset/DatasetModeStore.swift), the per-mode view files under
Views/Library/ViewModes/{Icon,List,Table,Columns}/, LibraryView+Insets.swift
(bottomInsetContent), LibraryView+KeyboardShortcuts.swift
(servicesRowKeyboardGrammar/usesSpatialProjection).
Behaviors (each → one pinning test)
A. The mode set and its one dispatch point
library.modes.eleven-selectable— [OK]ViewDisplayMode.selectableCasesis exactly Icon, List, Table, Columns, Sheet (.grid), Cards, Timeline, Calendar, Map (.geoMap), Canvas, Space — eleven live, user-choosable modes; the twelfth case,.workspace, is a decode-only legacy alias that normalizes to.canvasand is never itself selectable. Pinned:ViewDisplayModeTests.testSelectableCasesAreTheCoherentSet,ViewDisplayModeTests.testAllCasesNoLongerIncludeRetiredAliases.library.modes.one-dispatch-switch— [OK] every mode is mounted from exactly ONEswitch displayModeinlibraryRowsOrEmptyState(LibraryView+ContentBranches.swift:105-128), and the empty-collection state is checked BEFORE that switch, so no mode branch can ever substitute for “there is nothing here yet.” Verified each case mounts a real view, not a stub:.icon→iconsView,.list→listView,.table→tableView,.columns→columnsView,.grid/.cards/.timeline/.calendar/.geoMap→datasetModeView(_:)(one shared renderer host),.canvas/.workspace→canvasModeView,.space→spaceModeView. Pinned:LibraryImportAffordancesTests.testEmptyLibraryShowsEmptyStateBeforeAnyViewMode(asserts both branches exist and the empty check precedes the mode switch, by source position).library.modes.dataset-renderers-share-one-host— [PARTIAL] (→ #4865) the four dataset modes plus Map are not four separate view types but oneDatasetModeViewparameterized byDatasetRenderer(.grid/.cards/.timeline/.calendar/.map,DatasetModeStore.swift:9-11), scoped to the same folder query, the same live change stream (documentStore.revision), and the same open/open-source callbacks as every other mode. Grounded in readingLibraryView+ContentBranches.swift:243-280directly (datasetModeView(_:)’s single call site per case, same construction for all five); no dedicated render/dispatch test exists yet, so PARTIAL rather than OK.library.modes.canvas-and-space-are-gated-renderer-pairs— [PARTIAL] (#4005, #3087) (→ #4865) Canvas and Space each mount ONE of two renderers behind a feature flag reading the SAME shared stores (libraryProjection,canvasLayoutStore,canvasItemStore) — Canvas:CanvasSceneView(RealityKit-ortho) whenisCanvasRealityKit2DEnabled, else the SwiftUISpatial2DCanvas; Space:CanvasSpaceViewwhenisCanvasRealityKit3DEnabled, elseSpaceSceneView— so switching the engine flag is transparent to the mode itself (LibraryView+CanvasModes.swift:108-168). Verified at HEAD: both renderer pairs are live, neither is dead code — this matches the still-open legacy-milestone issues asking to finish the RealityKit cutover (P2 in the former “Library View - Spatial” milestone), which is a completion question for PASS 2, not a defect in what exists today.
B. One node, one set of chrome, across every mode
library.chrome.one-bottom-bar-every-mode— [OK] the bottom action bar is mounted UNCONDITIONALLY inbottomInsetContent(LibraryView+Insets.swift) — no branch ondisplayModereaches it, so every mode (including the dataset cluster, which the maintainer specifically re-tested) shows the identical bar, never a mode-specific substitute. Pinned:LibraryPaneSurfaceGuardTests.oneBottomBarForEveryMode,LibraryPaneSurfaceGuardTests.showControlIsNotModeSwapped.library.chrome.one-row-menu-builder— [OK] every browse mode (icon/list/table/ columns) and both canvases build a row’s context menu from the ONE shared builder — the canvases add at most one verb of their own and defer the rest — and every dataset renderer likewise builds from the ONE sharedDatasetRowMenu, a deliberately narrower vocabulary than the browse menu, not a second copy of it. Pinned:LibraryMenuParityTests("every browse mode and both canvases build their row menu from the ONE shared builder","the canvases add exactly one verb of their own, and defer the rest","every dataset renderer builds its menu from the ONE shared DatasetRowMenu","the dataset verbs are spelled in the shared builder and nowhere else").library.chrome.select-all-follows-the-visible-surface— [OK] ⌘A selects everything VISIBLE in the focused surface, mode by mode, not the folder’s full document set regardless of what’s on screen — fixing two costumes of the same defect: a dataset mode’s own date/prototype filter used to be bypassed (⌘A selected the unfiltered folder), and a bounded 3D board used to let ⌘A reach past its own render cap. Pinned:SelectAllVisibleSurfaceTests(four cases spanning dataset filtering and the 3D board’s cap),LibraryMenuParityTests.selectAllHasOneOwner("⌘A has one owner — canvas defers to the menu command (M1 resolved)").library.input.keyboard-grammar-is-explicit-per-mode— [OK] the row-ordinal keyboard grammar (arrows, type-ahead, Return-to-open, Space-to-preview) is serviced by the ordered modes (icon/list/table/columns) and deliberately NOT by the spatial modes (canvas/space), where an arrow key used to silently move the list selection while the user looked at a spatial layout; every mode has an explicit, deterministic answer, and no mode claims both spatial and row-keyed at once. Delete and the focused-item menu actions are NOT scoped away in spatial modes — they act on the shared selection, which is meaningful everywhere. Pinned:LibraryInputScopeTests("spatial modes do not service the row keyboard grammar","ordered modes keep the row grammar","every display mode has an explicit answer","no mode is both spatial and row-keyed","delete and focused actions survive in spatial modes").library.list.scroll-policy-is-deliberate— [OK] list-mode selection changes do not scroll the viewport as a side effect of every mutation — removing a row from the selection, clearing it, or a same-primary shift-extend all leave the viewport where it was; only a selection written from elsewhere (e.g. a restored launch selection, or a cross-surface jump) scrolls the new selection into view. Pinned:ListSelectionScrollPolicyTests(seven cases: removal, clear, click, shift-extend, external write, launch restore, and"list mode consults the policy instead of scrolling unconditionally").
C. Search scoping across modes
library.search.spatial-modes-share-one-projection— [OK] all three spatial-adjacent modes (the two canvases and the dataset cluster) build their filtered view from ONE projection of the already-filtered document set under an active search — not three independent filters that could drift from each other or from what the browse modes show for the same query. Pinned:ModeScopeFollowsSearchTests("the spatial projection is built from the FILTERED documents","all three spatial modes go through that one projection","the projection cannot drift from the filter that feeds it").library.search.columns-show-hits-not-browse-root— [OK] under an active search, the Columns mode’s first column shows the search hits, not the ordinary browse root; drilling into a hit resumes ordinary browsing from there. The dataset modes keep their own, separate hit-id seam rather than sharing the Columns one. Pinned:ModeScopeFollowsSearchTests("column 0 shows the hits while a search is up, not the browse root","deeper columns still browse — drilling into a hit is browsing again","the dataset modes keep their own hit-id seam").
D. Per-pane override and persistence
library.modes.per-pane-override— [PARTIAL] (→ #4865) a workspace can request a specific display mode for its OWN library pane (PaneConfig.libraryLayout→ViewDisplayMode(paneLibraryLayout:),App/ViewDisplayMode.swift:165-196), read through thepaneLibraryLayoutenvironment key, andLibraryView.displayModeprefers it over the window’s global mode unless the user has set an explicit per-pane override (paneDisplayModeOverridewins over both). Grounded in readingApp/ViewDisplayMode.swiftandLibraryView.swift:35-51directly; no test exercises the three-way precedence (paneDisplayModeOverride>paneLibraryLayout>defaultDisplayMode) end to end, so this is PARTIAL rather than OK.library.modes.legacy-raw-values-reset-not-migrate— [OK] an old persisted mode string (“Map”/”Spatial”/”RealityKit” from before the Canvas/Space split) decodes tonil(falling back to the default mode), not silently folded onto.canvas— a deliberate clean-start decision recorded because the library data wasn’t yet in production use when the vocabulary settled. Pinned:ViewDisplayModeTests.testLegacyRawValuesNoLongerDecode,ViewDisplayModeTests.testCanonicalRawValuesDecode.library.modes.persists-until-changed— [PARTIAL] (fixed ed67ec111; #4575 stays open for the maintainer to confirm on screen) the view mode is ONE choice per window and must never change itself — ordinary folder navigation only RE-NORMALIZES the current mode for availability (falls back only when the mode genuinely can’t render the new context), per the explicit 2026-08-09 ruling recorded atContentView+StateEvents.swift:34-41. The additional, verified live defect this pass found — selecting a sidebar KG collection (Entities or Claims) unconditionally setviewDisplayMode = .list, overriding whatever the user had chosen, with no relationship to the per-folder-restore case the 2026-08-09 ruling already fixed — is now fixed too: the line dated from when the Entities table was drawn INSIDELibraryViewand only rendered in list mode; Entities and Claims are their own content kind now and never read the mode, so the forcing line is gone and the mode is one choice per window again, for BOTH triggers this behavior named. PARTIAL rather than OK: not independently confirmed live by the maintainer yet — and the maintainer’s live test this morning (2026-09-19, after ed67ec111 landed) found a BROADER symptom than either trigger this behavior names: in the Library he cannot switch to icon, list, or any view mode but table — everything renders as table. Seen on screen; ed67ec111 was checked and RULED OUT as the cause. Design root, verified:availableViewDisplayModes(ContentView+StateLayout.swift:71-88) returns[.list]wheneverisKGLibrarySelection(driven by the window-scopedselectedKnowledgeKind) is true, gating the AVAILABLE modes for every Library pane in the window from one shared value — the same “one window-wide value, no per-pane seam” rootpanes-workspaces.md’spanes.model.per-pane-scope-and-kind-unreaddocuments for content kind and library id. Whether the peer-columns sizing change (0dc9adefa) compounds the symptom is unconfirmed, noted as a possibility to check (comment posted on #4575). Pinned:DocumentStoreAndSidebarTypesTests.testEntityLibrarySelectionDoesNotForceDisplayMode(filefichero/Tests/Unit/general/Models/DocumentStoreAndSidebarTypesTests.swift, suiteDocumentStoreAndSidebarTypesTests).
E. Icon mode (legacy milestone “Library View — Icons”)
library.icon.arrow-nav-latency— [BROKEN] (#4590, #4584) arrow-key traversal and Quick Look navigation in Icon view lag noticeably versus Finder; the selection change should be synchronous and only the image LOAD should be async — the same synchronous-selection/ async-load split the Preview pane needs for its own click-to-load latency (see section K: that half is a Preview-surface question, not this spec’s). Not verified as built.library.icon.thumbnail-preload-bounded— [GAP] (#4589) a folder’s thumbnails should prefetch (bounded, windowed) on open so scrolling doesn’t lazy-load-jump. Not verified as built.library.icon.selection-chrome-hugs-the-image— [GAP] (#4588) the selection border should wrap the artwork itself, Finder-tight, not a padded background tile. Not verified as built.library.icon.shift-click-extends-to-item— [BROKEN] (#4582) shift-click in Icon view should extend the selection to the clicked item by visual position (Finder’s icon-view semantics), not select the entire contiguous row the way List view’s shift-click correctly does — Icon view is currently borrowing List’s row-range semantics for a grid layout where they don’t apply. Not verified as built.library.icon.context-menu-builds-off-main-thread— [BROKEN] (#4585) right-clicking a 5-6 item selection beachballs — context-menu construction is doing per-item/per-selection synchronous work on the main thread; the menu must build from in-hand state with any counts/scope resolution async. This is a performance defect IN the one shared row-menu builderlibrary.chrome.one-row-menu-builderalready names, not a second menu system. Not verified as built.library.chrome.thumbnail-fallback-shows-type-icon— [GAP] (#4204) when a document has no thumbnail, the fallback should be that document’s OWN type glyph (a.docxshows a Word icon, an unrendered image shows an image icon) rather than one generic photo placeholder — a fact about the document lost, not only a style choice. Applies to icon tiles, list rows, and table rows alike (filed under the Icons milestone, but cross-mode by the issue’s own scope). Not verified as built.
Left in its legacy milestone, not folded here: #4581 (“Clear Data” bulk-delete of
content/attributes/entities for a multi-selection) is an audited bulk-CRUD action, not a
view-mode display question — no existing spec (this one, sidebar-crud.md, kg-tables.md)
squarely owns “bulk-clear a document’s derived content while keeping the document,” so it
stays on its legacy milestone for maintainer triage rather than forced into any of them.
F. List mode (legacy milestone “Library View — List”)
library.list.shift-click-anchor-established-first— [BROKEN] (#4592) the FIRST shift-click after a fresh selection sometimes selects only the clicked row instead of the range, because the range anchor isn’t established from the current single-focused row before the first extend — a classic missing-anchor bug; the SECOND shift-click works. Not verified as built.library.list.row-content-is-configurable— [GAP] (#4398) a list row’s badges (Completed/Processing) carry no information the spinner doesn’t already show and should be silent in the ordinary case; only an error is worth a row affordance, and it must be clickable to say what failed. The real fix underneath is letting the user choose which columns/properties a row shows (name, kind, size, dates, status, entity/page counts), from the SAME vocabulary the inspector uses — not a second naming of the same fields. Not verified as built.
Verify-close candidate, left OPEN for the maintainer, not closed here: #4376 (“⌘A must
Select All in the focused surface”) — the LIBRARY half is verified built at HEAD:
library.chrome.select-all-follows-the-visible-surface (above) is exactly this behavior,
pinned by SelectAllVisibleSurfaceTests and LibraryMenuParityTests.selectAllHasOneOwner.
The issue’s SECOND half — ⌘A selecting all text in the Reader — is a different surface this
spec doesn’t cover; not verified either way, so the issue as a whole should stay open until
the Reader half is checked, even though the Library half this spec owns is done.
Left in its legacy milestone, not folded here: #4311 (cross-library drag-and-drop copy,
and drag-out-to-Finder) has no clean home among this spec, panes-workspaces.md,
sidebar-crud.md, or research.md — it’s a Library drag-and-drop capability, not a view-mode
question, a pane/window question, a sidebar-tree CRUD question, or an agent-tool question.
Left for maintainer triage.
library.modes.quality-pass-tracked-here— [GAP] (#4160, #1971, #114 — three umbrella audit/quality-pass EPICs over the years, none superseding the others, filed from “every view mode as good as the sidebar” through “audit icon/list/table use standard SwiftUI controls” to a bare “[QA] Library View Surface Audit”) this spec’s own growing behavior set (List/ Icon/Table/Columns/Canvas/Space quality lines above and below) is now where these EPICs’ per-mode asks are tracked; none is itself a separate behavior to build.library.icon.zoom-scale— [PARTIAL] (#1930) per-view zoom via +/- and pinch-to-zoom, with larger thumbnails available. Verified at HEAD:@AppStorage("library.iconViewScale")(LibraryView.swift:336) persists the scale; a live pinch gesture drivesliveIconScalewith a clamp so a single thumbnail never exceeds the visible area (LibraryView+IconMode.swift:19-34) — built. No test found exercising the pinch gesture or the persisted scale, so PARTIAL rather than OK. The “grid sidebar view” half of the issue (a Finder-style icon-grid sidebar, distinct from the Library pane’s own icon mode) was not found and may be a separate, still-open ask.library.list.selection-and-save-reliability— [GAP] (#1961, an older, broader complaint than the specific anchor buglibrary.list.shift-click-anchor-established-firstabove pins) list click/selection/save described as “overloaded” and unreliable. Not verified as built or fixed; kept as its own line rather than folded into the anchor bug since the issue names selection AND save, not only range-selection.library.chrome.excluded-from-processing-treatment— [GAP] (#1791) a document excluded from processing should read as such wherever it appears — a visual treatment in the library, and it should not surface in ordinary search or the knowledge graph. Not verified as built.library.chrome.batch-operations-beyond-workflows— [GAP] (#1695) selection-scoped batch operations beyond running a workflow — delete/archive/tag/exclude on a multi-selection — from the shared bottom action bar (library.chrome.one-bottom-bar-every-modeabove). Not verified as built.
Verify-close candidate, left OPEN for the maintainer (evidence posted as a GitHub comment),
not closed here: #1931 (“Column view (Miller columns, horizontal), DEVONthink/Mail
style”) — an OLDER filing of the same ask library.modes.eleven-selectable/library.modes
.one-dispatch-switch above already confirm is built (.columns, columnsView), and the
same caveat as #3697 above applies: library.columns.seeds-from-the-browsed-folder (#4594)
may be why it still reads as broken to whoever tests it next.
G. Columns mode (legacy milestone “Library View — Column Browser & Columns”)
library.columns.seeds-from-the-browsed-folder— [BROKEN] (#4594) Columns mode should show the CURRENTLY BROWSED folder’s contents, the way Icon/List/Table all do — not always the library’s top-level root with the browsed folder absent from the path entirely. Verified BROKEN at HEAD:columnsRootDocuments(LibraryView+ColumnsSeeding.swift:24-28) always returnsdocumentStore.collections(the TOP-LEVEL root) for column 0, andseedColumnsPathFromSelection()only seeds a path from the current SELECTION’s ancestry — if the user has merely BROWSED into a folder via the sidebar with nothing selected inside it, no path is seeded and column 0 shows the unrelated top-level root, matching the reported repro exactly (a folder with 153 pages showing fine in Icon/List, empty in Columns). Root cause is the seeding source (selection, not the browsed folder), not a query bug.library.table.facet-columns-from-knowledge-objects— [GAP] (#3700) a document’s entity/annotation/note/bbox counts should be exposable as columns/facets in the table and column browser, reusing the knowledge objects already in the store. Not verified as built.library.table.column-customization— [GAP] (#3698) a column picker should choose which columns the table view shows (name, dates, type, entity count, size, etc.), persisted per view. Not verified as built.
The table’s Done column (one badge per step run on a document) belongs to
activity.document.what-has-been-run in ui/activity-and-automatic-work.md (#5434); it is
specified there, not here.
Verify-close candidate, left OPEN for the maintainer, not closed here: #3697 (“Column
Browser view mode (Finder-style Miller columns)”) — the MODE ITSELF is verified built at
HEAD: .columns is a live ViewDisplayMode.selectableCases member, mounted via
columnsView in the one dispatch switch (library.modes.eleven-selectable,
library.modes.one-dispatch-switch above). What is NOT verified is whether it was fully
working when this old issue (2026-07-13) was filed, or whether library.columns.seeds-from-
the-browsed-folder’s bug (above) is the reason it still reads as broken today.
H. Dataset modes — Sheet, Cards, Timeline, Calendar, Map (legacy milestone “UX - Representations”)
library.dataset.timeline-controls-broken— [BROKEN] (#4598) in Timeline mode, changing sort has no effect, “show full text” has no effect, and there is no multi- or discontiguous selection or editing. Also unconfirmed: whether dated entries surface regardless of provenance (date sidecars/manifest as well as workflow-extracted dates) rather than only the latter. Not verified as built.library.dataset.sheet-rows-are-multiline-and-sortable— [GAP] (#4595, #4596) Sheet mode’s rows should show their full multi-line text (top-aligned), and clicking a column header should sort by that column, toggling ascending/descending — ordinary Mac table behavior neither piece has today. Whether SwiftUI’sTablecan host variable-height editable multi-line cells is an open implementation question the issue itself raises; worth a design escalation if not. Not verified as built.library.dataset.calendar-interactions— [GAP] (#4599) Calendar mode should support Space to Quick-Look a date, click-through to BOTH Reader and Preview (today only Preview follows a click), filter/search within the view, and zoomable granularity from day through century — an archive spanning decades needs the wide zooms. Not verified as built.
Verify-close candidate, left OPEN for the maintainer, not closed here: #2667 (“EPIC:
collapse view modes to Canvas (2D) + Space (3D), one positioned-node model, shared xpos/ypos”)
— its core frontend asks are verified DONE at HEAD: ViewDisplayMode has exactly Canvas and
Space as the two spatial modes today (no separate .map/.spatial/.realitykit — the
epic’s own “current state” table describing three overlapping modes no longer matches the
code), and both already share canvasLayoutStore/canvasItemStore per
library.modes.canvas-and-space-are-gated-renderer-pairs above. The epic’s backend
persistence-audit task and its deferred endpoint-renaming task were not independently
re-verified this pass.
Left in its legacy milestone, not folded here: #2807 (iOS first-run parity, an onboarding
product decision) and #1755 (georeferencing a scanned map image onto a real basemap/globe
— an authoring feature, not a mode that plots existing coordinates the way Map mode does) are
both waiting on a home this spec isn’t: onboarding has no spec, and georeferencing needs a map/
representations spec of its own — corrected 2026-09-19: source/historical-text-normalization.md
was written and read in full; #1755 is unrelated to it (that spec is about historical TEXT
normalization — dates, entity variants, scripts — not map images), so this is not its waiting
issue either. Left on its own milestone (“UX - Representations”, #183). Since 2026-09-27 its DATA
model has a home: source/maps-and-georeference.md (#5120). The map surface is still
homeless until a map-view UI spec exists (#5128).
I. Canvas & Space (legacy milestones “Library View - Spatial”, “Library View - Canvas”)
library.canvas.positions-persist— [OK] (c0fc9af1f, #5301) a card dropped on the Canvas or in Space stays where it was dropped: after the save returns, after leaving and re-entering the folder, and after a relaunch. One layout per folder scope (the whole library is__library__), stored in the library, so the arrangement travels with the library file.- Why it broke (2026-09-30): the app saves and reads a card under its node id (
doc:<id>,entity:<id>), and the engine accepted only bare row ids, so it skipped every card inside a 200 and the card snapped back. The engine now accepts the app’s node ids and stores them as sent; the app reads rows back under the same id. - A card the engine cannot place (a node id for nothing) is reported, not silently dropped: the app does not treat a skipped card as saved.
- Pinned by
test_the_apps_own_card_ids_are_saved_and_read_back(engine, realdoc:ids through the real route). library.canvas.arranged-at-import— [PARTIAL] (#5585) a folder an import fills (folder ingest, any mode: link, copy, move, index) opens on its canvas with every item placed, as filed: the import runs the one arrange action (canvas.arrange) for each folder it filled that has no saved layout, laying its children out in folder order on a grid under the app’s card ids (doc:<id>). A folder with a saved layout (someone laid it out, or an earlier import did) is never touched; items a later import adds to it stay “not placed” until someone arranges it. The destination folder the import lands in is not new and is left alone. An arrangement that fails does not fail the import (the files are in); the action’s result names each folder not arranged and why (not_arranged).- Why (2026-10-07): after linking a 203-page box, the canvas picture showed all 203 pages “not placed”: nothing had ever saved a place for them.
- Tested through the real
import.folderaction and canvas-layout route (fichero-server/tests/unit/importers/test_a_new_folder_is_arranged_as_filed.py), not yet seen in the app. Not covered: a corpus (manifest.jsonl) or 1.0-archive folder, which take the manifest importer. library.canvas.arrange-is-an-action— [MISSING] (#5302) choosing Arrange by (As Filed, Name, Type, Date) lays EVERY card of the board out in that order and saves those places, as Finder’s Clean Up By does; after that each card can be dragged anywhere and stays there. Today an arrangement only places cards that were never moved (“a saved row always wins”), so once positions save, an arrangement would appear to do nothing to the cards a person has touched. One step, undone by one ⌘Z. Both canvases read the same saved places.library.canvas.arrange-by-date— [MISSING] (#5302) Date orders cards by the document’s own date where it is known (the Date column’s key), else by when it was added, undated last.library.canvas.a-folder-previews-as-its-canvas— [PARTIAL] (#5300) a selected folder shows in the Preview as its own 2D canvas: its items as cards where they were last placed, movable there, and the move is the same saved move as in the library’s Canvas mode (one layout per folder, one write path). Clicking a card selects that item, and the Preview then shows the item. Depends onlibrary.canvas.positions-persist: a canvas that forgets what you moved teaches the wrong thing.- Why the cards jumped on zoom (#5476, 2026-10-05): the canvas resolved its board only inside
the renderer’s update step, which nothing watched. The pane’s size, page shapes that landed late
(they set the grid’s spacing) and saved places that loaded late all changed without a redraw, and
the next unrelated update, a pinch, applied them at once. Two tall pages laid out before their
shapes were known also stayed one column on the short spacing and overlapped. The board is now
resolved where every input is watched, page shapes are watched, and nothing draws before the pane
has a size, so a zoom moves the camera and no card. Tested in
fichero/Tests/Unit/general/Views/Library/CanvasZoomKeepsTheCardsTests.swift(the real view, store and renderer), not yet seen in the app. library.canvas.cards-move-only-when-asked— [PARTIAL] (#5629; tested, not yet seen in the app) Ruled 2026-10-09: on the Canvas and in Space a card moves only when a person moves it: a drag, an Arrange, or a move made in another window (someone’s drag too). The board opens with every card already where it stays and the camera at its fit: the first frame is the settled one, and no card is drawn and then moved. After that nothing else moves a card: a pane resize, a card arriving (it takes a free cell of the grid the board opened with, shifting nothing), page shapes loading, a refresh, a selection, a zoom, a save’s round trip. The camera fits once, on open (or returns to where the person left it,library.canvas.a-board-remembers-its-camera), and never re-fits unasked.- Why it jumped (2026-10-09): the canvas drew on its first frame, before the folder’s saved places and items had loaded and before any page’s shape was known. It drew the default grid; the saved places then arrived and the cards slid to them (animated, many at once); page shapes then widened the grid and the board re-flowed, with the camera re-fitting behind it. The default grid also followed the pane’s shape and the number of cards, so a resize or a new card re-laid it.
- Now: the canvas draws nothing until its scope’s saved places and items have loaded, the first cards’ page shapes are known (at most 120 pages, waited for at most 1.5 s) and the groups’ pages have loaded (each wait bounded); the opening placement is never animated; every card is then held where it was first drawn unless a saved place says otherwise; the camera fits once.
- Not covered: a page whose shape arrives after the open (past the 120 or the 1.5 s) keeps its place, so a much taller or wider page than its neighbours can overlap them until arranged.
- A drag is per-card work only: the dragged card’s entity moves and its handles move with it;
nothing is rebuilt, re-resolved or saved until the drop (
CanvasDragFollowsPointerTests.swift). - Tested in
fichero/Tests/Unit/general/Views/Library/CanvasOpensSettledTests.swift(the hold, the gate, the opening reconcile, the camera) andCanvasZoomKeepsTheCardsTests.swift(the real view: late page shapes and a pane resize move nothing). library.canvas.one-canvas-everywhere— [PARTIAL] (#5629; tested infichero/Tests/Unit/general/Views/Library/CanvasBoardTests.swift, not yet seen in the app) Ruled 2026-10-09: the folder canvas in the Preview and the Library’s Canvas and Space modes are one canvas. Same view (CanvasSceneViewfor 2D in both hosts), same renderer, same cards and thumbnails, same selection ring, same drag, same never-move rule (library.canvas.cards-move-only-when-asked), same context menus. Both hosts build the board (cards, groups, drop-into containers, colouring) with the one builder,CanvasBoard, from the documents they show; the Library’s whole-library board adds its entities.- Why (2026-10-09): the two hosts built the canvas’s inputs separately. The Preview passed no drop-into containers, so a card could not be dropped into a subfolder there, and every later difference would have been a second canvas.
- Not covered yet: the Preview’s canvas has the canvas’s own menu verbs but not the document
menu (
library.canvas.context-menus). The boxes the maintainer saw over cards in the Preview’s canvas are not explained: the canvas draws no segment boxes, and the Preview hosts the same view. library.canvas.context-menus— [PARTIAL] (#5632; tested infichero/Tests/Unit/general/Views/Library/CanvasContextMenuTests.swiftandLibraryMenuParityTests.swift, not yet seen in the app) a right-click or Control-click on the canvas, 2D and 3D and in every host, opens a menu. On a card (a card not already selected is selected first, as in Finder): Zoom to Card, then the card’s document menu, the one shared menu every Library mode and the sidebar’s parity rows show (no canvas-only document actions). On the board: New Note, Arrange By (As Filed, Name, Type; choosing one lays the board out and saves it, the explicit actlibrary.canvas.cards-move-only-when-askedallows), Zoom to Fit, Actual Size. The canvas’s own verbs live in the canvas (CanvasContextMenu), so the Preview’s folder canvas has them too.- Not covered yet: Paste and New Folder on the board; the Preview’s folder canvas has no document menu on a card (the shared document menu is the Library pane’s and is not reachable from the Preview yet); Space has no right-click hit test, so there the menu is the card’s whenever something is selected.
library.canvas.selection-ring-is-thin— [PARTIAL] (#5631; tested, not yet seen in the app) a selected card on the 2D canvas wears a thin system-style ring, about 3 points in the accent colour, just outside the card’s edge and following its rounded corners, the same on-screen thickness at every zoom. The card itself does not change size or place.- Why (2026-10-09): the selection was an accent plate padded by 8% of the card in world units, so zoomed onto a page it showed as a solid band about 30 points wide.
- Tested in
CanvasOpensSettledTests.swift(the ring’s geometry, and through the real renderer across zooms). library.modes.the-view-follows-the-node— [PARTIAL] (#5428; built 33310028f, tested infichero/Tests/Unit/general/Views/Library/LibraryViewFollowsNodeTests.swift, not yet seen in the app) Ruled 2026-10-04, Finder-like: clicking a source (an image or a PDF, the same rule) in the sidebar shows its parent folder in the Library pane, in that pane’s saved view mode (canvas, table, …), with the source selected there; the linked Preview shows the source. If the window has no Preview pane, one opens. Clicking the folder shows the folder, still in its saved mode. This replaces narrowing the library to the one clicked item (#156) for a sidebar source click, so an image never shows an empty canvas. A double-click into a PDF in the Library still browses its pages. Test through the real sidebar selection and the view-mode store, not an injected mode.library.canvas.cards-take-clicks-and-drags— [PARTIAL] (#5305) a press on a card selects it, drags it or (twice) zooms it; a press on the empty board draws a rubber band; pressing one card of a multiple selection moves the whole selection. Built 572719bca from the canvas’s own hit test (the entity-targeted gestures stopped reaching the cards); the resize handles are not on it yet.library.canvas.a-board-remembers-its-camera— [OK] (572719bca) a board opens fitted to the pane with margin; coming back to a folder returns to the centre and zoom the person left it at (kept per Mac, not in the library).library.canvas.group-into-one-node— [PARTIAL] (#5303) selecting images and choosing Edit ▸ Group (no chord yet: ⌥⌘G, Keynote’s and Freeform’s, is the Preview markup Line tool today) makes ONE group node that holds them in order, in the place of the first, as a letter holds its pages; Edit ▸ Ungroup returns each to where it was. The node already existed:DocType.group(“logical document: a letter = multiple pages”), made by the library’s context-menu Group as Stack throughPOST /api/documents/groupsand undone by…/groups/{id}/ungroup, which records every member’s old place. The menu items and the canvas selection are new (2026-09-30). How a group then shows everywhere is designed below (Groups are containers, #5570).
Groups are containers (2026-10-07, #5570, #5569). A group is a document made of pages: a letter,
a judgment, a register entry. It was made by Group as Stack and then drawn as a broken image
(“Preview unavailable — Source file not available”), hidden from the sidebar, with its pages out of
sight and its place lost to the name sort. The case that showed it: Istmina Full’s ‘1948 Sentencias’,
203 loose pages organised through the engine into 8 judgments of 6 to 62 pages each. The design, in
one sentence: a group is a folder whose order matters and whose pages read as one document. Every
surface that decides how a node renders by its type treats DocType.group as a container, never as
a file.
- library.group.lists-as-a-container — [PARTIAL] (#5570; built 2026-10-07, not yet seen in the
app) in the sidebar, the list, the table and the columns a group shows as a group: its picture is
its first page with the stack symbol (rectangle.stack) and its page count beside it, it carries a
disclosure triangle, and it expands to its pages in their order (sort order, never name). The
sidebar used to drop groups (and with them their pages) because only folders and PDFs counted as
containers; Document.isNavigableContainer now includes groups, so the sidebar, the table outline,
the selection’s child load and double-click all answer from the one predicate. Tested in
fichero/Tests/Unit/general/Views/Library/GroupsAreContainersTests.swift (the sidebar’s tree
builder, the thumbnail classifier, the table outline).
- library.group.double-click-opens-its-pages — [PARTIAL] (#5570) double-clicking a group opens
it as a folder-like scope: the Library pane lists its pages, in their order, in that pane’s view
mode, and the crumbs name the group. In Columns a group opens the next column like a folder.
- library.group.previews-as-its-pages — [PARTIAL] (#5570) a selected group shows in the Preview
as its pages, on the group’s own canvas (the same board as
library.canvas.a-folder-previews-as-its-canvas, one layout per group): its saved layout when it
has one (each Istmina group holds an 8-column grid), else the default grid in page order. A click
on a page selects it; a double-click shows that page. Never a missing-file message. Tested by
GroupsAreContainersTests.groupPreviewsAsItsCanvas (the Preview’s route).
- library.group.cover-is-its-first-page — [PARTIAL] (#5570) the engine answers a group’s
thumbnail and display image with its first page’s (lowest sort order), so every surface that draws
a picture for a node draws one for a group without knowing what a group is. Pinned by
test_a_groups_picture_is_its_first_page (engine, real routes).
- library.group.reads-as-one-document — [PARTIAL] (#5570) the Reader reads a group’s pages as
one document: the engine’s transcript view assembles a group’s pages (its children in their order)
as it assembles a PDF’s pages, with a page anchor each, so a judgment of 12 photographed leaves
reads top to bottom. A folder still reads as itself (ReaderFolderProxy); a group is not a
folder. Pinned by test_the_reader_reads_a_groups_pages_as_one_document.
- library.group.takes-its-first-pages-place — [PARTIAL] (#5569 item 1) a new group takes the
place of its first page in the folder. A folder nobody had reordered held every item at sort
order 0 and listed by name, so the group had no place to take; the engine now first gives that
folder an explicit order (each item its listed position) and the group takes its first page’s
slot. The other members leave gaps, which Ungroup fills again, so Ungroup puts every page back
where it was. Inside the group the pages keep the order they were grouped in. A folder already in
a strict order is left as it is. Pinned by
test_a_new_group_takes_its_first_pages_place_in_an_unordered_folder,
test_ungroup_puts_every_page_back_in_its_place.
- library.group.sorts-by-order — [PARTIAL] (#5570) the library’s sort menu has As Filed
(the folder’s sort order, then name in natural order), and it is the default for a folder with no
saved sort, so groups and pages appear in their order, not alphabetically. Name and the other
sorts remain; a folder’s chosen sort is still remembered. Tested by
GroupsAreContainersTests.asFiledOrdersBySortOrder.
- library.group.dated-by-its-pages — [PARTIAL] (#5569 item 2) a group carries a date: its own
when one is given to it (a judgment’s date), else the range of its pages’ dates (“2 March 1948 –
15 March 1948”), through the ordinary date fields (date_jdn, date_jdn_end, date_meta with
source pages), so the Date column, the date sort and the timeline show it with no special case.
A pages’ range is re-read from the pages on every listing, so a group made from undated pages
shows their date once they are dated. Pinned by test_a_group_of_dated_pages_carries_their_range,
test_a_group_made_before_its_pages_were_dated_reads_their_date,
test_a_group_with_its_own_date_keeps_it.
- library.group.answers-its-size — [PARTIAL] (#5569 item 3) creating a group answers its true
child_count, and fetching one document answers its child count too. Pinned by
test_create_group_answers_how_many_pages_it_holds.
- library.canvas.a-group-is-a-container-card — [PARTIAL] (#5570; built, not yet seen in the app)
on the Canvas a group is drawn as a container: a framed card with its pages laid out inside it, in
the group’s own saved layout (scaled into the frame) or else a grid in page order. A whole box (8
groups and their 203 pages) is therefore one board: zoomed out the groups read as framed tiles,
zoomed in the pages read as pages. The frame has the size of one card unless it was resized; a
resized group keeps its size, and its pages scale with it. Pressing a group or any page inside it
and dragging moves the group and its pages together, saved as ONE move of the group (the pages’
places inside it are the group’s own layout, never rows on the parent board, and Arrange by
leaves them out). On the parent board a page is part of its group’s card: a click, a drag or a
rubber band over a page acts on the group, and a double-click opens the group, whose own board
(the Library inside it, or its Preview) shows each page as a card of its own. The frame is a
light wash drawn behind its pages and is never coloured by Colour by. Tested in
fichero/Tests/Unit/general/Views/Library/CanvasGroupNestingTests.swift (the real scene
resolution and nesting), not yet seen in the app.
- Not yet: dragging a page OUT of a group to ungroup it, selecting a single page on the parent
board, pages scaling live during a resize (they follow on release), and the 3D Space renderer
(it still draws a group as one card showing its first page).
- library.group.undo — [PARTIAL] (#5303) Group and Ungroup are the engine’s audited
document.group / document.ungroup actions, each the inverse of the other (an Ungroup undone
re-groups the same pages under the SAME id), so the history and ⌘Z can reverse them. Not yet seen
end to end from the app’s ⌘Z.
- Next slice — [GAP] (#5570, specs/source/finding-documents.md) prototypes and attributes on
groups: a group can be given a kind (Sentencia) whose attributes (juez, fecha, partes, resultado)
it carries and the Inspector, the table’s columns and the canvas card show; the group’s own date
then comes from its fecha.
- library.rotate.command-r-and-l — [PARTIAL] (#5304; built, not yet seen in the app) ⌘R / ⌘L rotate the selected images 90°
right / left in the library (every mode), the Preview and the canvas, through the image editor’s
one rotate action.
- library.canvas.trackpad-scroll-pans — [BROKEN] (#4408) two-finger trackpad scroll
should pan the canvas with no modifier — the platform convention every Mac trackpad app
(Preview, Maps, Freeform, Figma, Photos) follows — while panning today requires holding
Space. Verified at HEAD: CanvasSceneView.swift’s panOrMarquee gesture gates panning
behind the Space key exactly as described; the issue’s own read of the code (a plain drag
must move items, not the camera, per an earlier, already-closed drag-vs-camera fix) is
sound and does not extend to SCROLL, a
different input that doesn’t compete with drag. Pinch-to-zoom already follows platform
convention; scroll does not — half a gesture vocabulary today.
- library.canvas.input-and-selection-polish — [GAP] (#4601, #4603) a consolidated input
program: rubber-band select with edge autoscroll and the app’s standard rubber-band style
(today’s reads as too dark and sometimes beachballs); a multi-select group gets ONE
bounding box, constant screen-space width regardless of zoom; grab handles on
single/multi-selection in both 2D and 3D; Space Quick-Looks the selection (today it beeps);
camera pan via click-drag requires Command; shift-drag constrains movement to one axis.
Several of these are new asks beyond what library.canvas.trackpad-scroll-pans covers.
Not verified as built.
- library.canvas.item-crud-affordances — [PARTIAL] (#3085) the backend CRUD exists —
CanvasItemStore.createItem/.updateItem/.deleteItem (CanvasItemStore.swift:159-266)
— but the UI affordances the issue asks for (add note/quote/text/link from a toolbar menu,
canvas context menu, or double-click-empty-space; inline text editing; resize via corner
handle) were not found: canvasContextMenu() (LibraryView+CanvasModes.swift:52-65) offers
only “Zoom to Card” and the shared document context menu, nothing that calls
createItem/updateItem. Store exists, no caller.
- library.canvas.universal-container-scope — [GAP] (#3091, #1773 — the same ask, #1773
the older filing, naming icon/column/map/spatial specifically for an entity-library
selection whose list mode already shipped) Canvas and Space should be offered as view modes
on ANY container — search results, an entity-library selection, a workspace — not only
folders; today search gets .map only behind an advanced flag and entity selection is
list-only. Extends library.modes.canvas-and-space-are-gated-renderer-pairs and
library.search.spatial-modes-share-one-projection above, which currently cover only the
folder-browsing case. Not verified as built.
- library.canvas.nested-container-navigation — [GAP] (#3092) entering a canvas item that
is itself a container (a folder, a PDF, an image/page) should switch the canvas to THAT
item’s own scope — its pages, its own notes/annotations/entities as the placeables — using
the same navigation spine Icon/List already use to drill in and out. Not verified as built.
- library.canvas.zoom-lod-tiers — [GAP] (#3105) smooth zoom into an image, resolving
glyph → thumbnail → full-resolution texture identically in Canvas and Space, with hysteresis
at tier boundaries and frustum/visible-rect culling so a zoomed view fetches only what’s
on screen. Not verified as built.
- library.canvas.tinderbox-features-beyond-mvp — [GAP] (#4192) the founding EPIC’s
vision beyond what’s already built (2D/3D renderer pair sharing one store, per
library.modes.canvas-and-space-are-gated-renderer-pairs): adornments (background regions
that group notes spatially), typed links with labels and visual variation, aliases (the
same note in several maps without duplication), and AI-initiated arrangement as a
first-class, attributable, undoable action. None of these were found built. The epic’s own
open design questions (shared vs. per-scope z-position, adornments as a new type vs.
reused folders, link-type vocabulary) remain genuinely open, not decided by this pass.
A page card drawing the page’s layers (boxes, names, statements, notes) is specified in
layers-on-the-source.md (layers.canvas2d.page-card-draws-layers,
layers.canvas3d.page-card-draws-layers).
Arrangements that propose (2026-10-06, #5550). A folder’s filed order is one order, and often
not the only one: two hundred loose documents can include the three pages of one letter, kept
apart. Both canvases offer these ways to arrange a folder beside placing items by hand, and any
proposal is a hypothesis: shown apart, attributed to its method, and nothing moves or merges until
a person accepts it.
- library.canvas.arrange-by-similarity — [GAP] (#5550, #5549) cards are placed so that near
means alike, from a chosen vector (the page’s picture, its text, its hand). The method and its
distortions are named, and the layout is saved like any other arrangement and undone with one ⌘Z.
- library.canvas.model-proposes-groups — [GAP] (#5550) a vision or language model proposes
groups of cards (same letter, same hand, same subject), each group with its reasons. Accepting
one moves its cards together and leaves them separate documents.
- library.canvas.orders-beside-the-filed-one — [GAP] (#5550, #5548) a proposed sequence is a
named order kept beside the filed order, never replacing it. A card can be stepped through in
either order, and the evidence for each adjacency (a continued sentence, a matching tear, a page
number, a date) is shown.
- library.canvas.pieces-of-one-document — [GAP] (#5550, #5303) cards proposed as the pieces
of one document (pages of a letter, a split register) are drawn joined. Accepting the proposal is
library.canvas.group-into-one-node, so the pieces become one group node in their proposed order,
and Ungroup restores each one.
library.space.wiring-completeness— [PARTIAL] (#3089).spaceIS a live selectable mode today, offered wherever.canvasis (ContentView+StateLayout.swift:84,89) and mounted viaspaceModeView. Not verified: a View-menu “Space” entry with a ⌘5 shortcut (no match found inViewMenuCommands.swift), and the acceptance demo the issue names — two windows on the same folder, one in Canvas, one in Space, each updating live from the other’s edits.
Verify-close candidates for the maintainer, cited but not re-litigated here: #3087 (“cut
over .canvas to SceneKit, retire Spatial2DCanvas”) and #4005 (“wire-in or delete the
legacy canvas fallbacks”) both name exactly the pending completion decision
library.modes.canvas-and-space-are-gated-renderer-pairs already documents — both renderer
pairs are live at HEAD, neither cutover is finished. Cite that behavior rather than add a
duplicate line for each issue.
J. Redirected elsewhere (existing specs)
Four issues from these eight milestones were re-read against their OWN best-fitting spec
rather than forced into this one — see panes-workspaces.md (panes.double-click-focuses-
current-window, #3364; panes.split.each-pane-its-own-document, #2422), research.md
(research.canvas-actions-are-agent-tools, #3093), and modes-to-panes.md
(m2p.grouped-nodes-drill-consistently-across-view-modes, #3699).
K. No spec exists yet — left on the legacy milestone, not moved
Four issues originally waited on a spec this ledger’s burn-down proposed but nobody had written yet, per the fold rule: do not invent a home, do not move the issue. One (#4364) has since been resolved and moved; the other three’s status is corrected below.
- #4364 (Historical dates: adopt undate/EDTF) — RESOLVED 2026-09-19:
historical-text-normalizationwas written (milestone #317) and #4364 has been moved onto it, cited fromhistnorm.dates.adopt-standards-format. No longer waiting. - #4236 (Search returns 0 results for text the Inspector is displaying) — waiting on the
proposed
searchspec. Left on milestone “Library View” (#117). - #1755 (georeference a scanned map as a real-world overlay) — corrected 2026-09-19:
does NOT wait on
historical-text-normalization— that spec was written and read in full; #1755 is map georeferencing, unrelated to historical text. This line was a wrong pointer, found and fixed while folding that spec’s own legacy milestone. Left on its own milestone (“UX - Representations”, #183). Its data model now lives insource/maps-and-georeference.md; the map surface waits on a map-view UI spec (#5128), as noted in section H. - #4583 (Preview pane: first click delay before the full image loads) — RESOLVED since
this was written: moved onto
preview-surface.md’s milestone aspreview.image.shows-cache-before-full-load. The SELECTION-latency half of the same symptom stays here aslibrary.icon.arrow-nav-latencyin section E above — cross-referenced from both sides, not duplicated.
L. No existing spec fits either — maintainer triage, not a guess
- #4581 (Clear Data bulk-delete) — see section E.
- #4311 (cross-library drag-and-drop, drag-out-to-Finder) — see section F.
- #2807 (iOS first-run parity, an onboarding product decision) — see section H.
- #2418 (cross-platform RTF-editor parity investigation) — a Reader/editor-capability
question, not a Library view-mode question; no spec (
reading-markup-annotations.md,library-view-modes.md, or anything else read this pass) squarely owns “is the iOS text editor as capable as the Mac one.” Left on milestone “Library View” (#117).
Cross-references — rulings honored, not restated here
- The Library pane is always the navigator —
modes-to-panes.md’s own ruling; this spec is entirely downstream of it (a view mode is how the navigator draws itself, never a reason for the navigator to stop being the navigator). - The knowledge graph’s timeline and map are ordinary Library view modes, and the legacy
KG browser/graph mode retired rather than living beside them —
modes-to-panes.md’sm2p.kg-graph-retires-as-library-takeover. This spec’s.timeline/.geoMapbehaviors above are the SAME modes that ruling already names; not duplicated as a separate claim. The force-directed graph as a Reader rendition of one focused entity is that ruling’s own future-work note, not something this spec re-opens. - Canvas and Space fold into the Library as view modes, not a separate top-level surface — already true at HEAD (section A above); the remaining legacy-milestone issues about them (RealityKit cutover completion, nested containers, agent/MCP access) are PASS 2 fold candidates, not open design questions about whether they belong here.
- Finder-like direct-manipulation principle (show all items, multi-select everywhere, one
gesture grammar) — invoked the same way
kg-tables.mdandsidebar-crud.mdalready invoke it, not re-derived as a new rule specific to view modes.
Future (ideas, not scheduled)
- (#4339) Finder-style group-by in the Library sort menu; Library browsing feature
- (#1972) Future section: image keywords, ratings and thumbs up/down curation in batch from browse/detail surfaces; issue itself says “future only”.
- (#4460) Future section: product decision on what order shift-click extends along on a spatial canvas (needs a total order); blocks canvas shift-click only.
- (#2290) 3D UMAP/t-SNE archive landscape over the existing LanceDB embeddings as one more spatial view-mode; product vision, not current work.
- (#2100) iPhone compact layout (split view to stack by size class); iOS adaptive shell is a later device-tested milestone, not current Mac work.
- (#2807) iOS first-run parity is a product decision (document as intended or build a compact first run); spec section H holds it, not scheduled.
Triaged from the backlog (2026-10-04)
library.inspector-scales-to-50k— [GAP] (#1790) Opening the inspector on a large folder (50k+ images) loads entities paged and non-blocking with a real loading state.library.input.type-select— [GAP] (#3689) list, table, grid and search-result rows support type-to-jump alongside arrows, Return to open and space to select (arrow navigation itself is library.input.keyboard-grammar-is-explicit-per-mode).library.input.one-grammar-every-mode— [GAP] (#4412) list, icons, columns, table, canvas, 3D and graph answer shortcuts, click/modifier selection and trackpad gestures the same way, pinned by one matrix test.library.perf.page-click-never-stalls-main— [PARTIAL] (#4280, #4574) clicking between PDF pages never blocks the main thread over ~1s and a superseded load costs nothing; cancel-superseded and single-flight images landed (31c6756f1, 7a189c2b0) but the latest baseline worst is 1860 ms.views.space-shares-canvas-position— [GAP] (#2391) the Space (3D) mode zooms in as far as Canvas and shares one xpos/ypos per node with it.library.chrome.clear-data-for-selection— [GAP] (#4581) a selection of documents offers one audited, undoable Clear Data action that removes extracted text, entity links (orphaned entities deleted), artifacts and dates for those pages, with no per-kind picker (ruled 2026-08-19).library.drag.across-libraries-copies-and-out-to-finder— [GAP] (#4311) dragging items to another library copies them (never moves, through the audited layer under the acting user, respecting permissions), and dragging to the Desktop or Finder exports regular files.library.drop-and-select-show-feedback-immediately— [GAP] (#4235) dropping a folder or selecting one shows something the same frame (placeholder or selection), then streams data, with no dead interval.library.list.scroll-is-smooth— [GAP] (#4593) list rows compute nothing per frame (date formatting, thumbnail decode, filters hoisted or cached) so scrolling holds frame rate.library.perf.no-main-thread-stall-over-33ms— [PARTIAL] (#4602) the ranked stalls (secondaryBarButtons, graph churn, entityKindSection) are removed; entities-tab stall fixed in d58a72d42, the rest unverified.library.modes.table-folder-click-is-instant— [GAP] (#5276) clicking a folder shows its known rows in the same frame at final row height with the island, breadcrumb and highlight agreeing mid-click; rows and thumbnails then update in place (the cached-rows half landed in b5760afdb, 89495ebea; the mid-click disagreement and measurement remain).library.modes.double-click-expands-or-opens-folder— [GAP] (#5278) a sidebar double-click on a folder toggles its disclosure and never opens a window (window stays on the context menu and command-double-click), and in the Icon, List and Columns library modes a double-click opens the folder at once (the Table half landed in 2eca400c0).library.modes.table-status-icon-content-and-hide-all— [GAP] (#5279) the Table has no Status column (removed in 4e42a8c7a, #5296: the Name cell carries the queued/running/failed mark), a text-only Content column apart from the NER columns, top-aligned rows, and a Hide All beside Show All in the column menu (most landed in 89495ebea; Hide All and the check remain).library.modes.table-expand-lists-contents-not-entities— [GAP] (#5282) expanding a folder in Table or List lists its files and subfolders to any depth, never entity or claim rows (two levels landed in 6540d91f5; child folders do not yet expand further).library.modes.bottom-pane-scrolls-to-its-last-row— [BROKEN] (#5004) a Library pane along the bottom scrolls to its end with the whole last row visible above the bottom bar (the scroll content takes a bottom inset).library.canvas.moving-one-card-moves-only-it[PARTIAL] (#5423; tested infichero/Tests/Unit/general/Views/Library/CanvasMoveOneCardTests.swift, not yet seen in the app): moving one card on the 2D canvas moves only that card and saves its place; every other card keeps where it was. The automatic layout places only cards that have never been placed. Tested by moving one card and checking that the others did not move.- Why it broke (2026-10-04): the saved places were right, but the board’s automatic camera re-fitted whenever the cards’ bounds changed. A moved card changes them, so the view re-centred and re-zoomed and every other card jumped on screen. A card the person moves or resizes now ends the automatic camera, as a pan does. Since 2026-10-09 the camera never re-fits after the open at all (
library.canvas.cards-move-only-when-asked). library.table.outline-cached-not-per-render[GAP] (#5255): in Table mode the outline rows, the dated and undated groups and the arrow-key row order are rebuilt only when the folder’s documents, the expanded set or the sort change, never on a click, so a 100,000-item folder stays as quick to click as a 144-page one.library.listing.slim-rows-paged-by-cursor[GAP] (#5260): a folder listing in the sidebar, the Library views and the dataset views fetches small rows (name, kind, parent, dates, child count, status, thumbnail), a screen at a time by cursor as it scrolls; a document’s metadata, runs and text come only when it is selected. A 100,000-item folder shows its first screen in under 100 ms.