diff --git a/docs/specifications/collection-views.md b/docs/specifications/collection-views.md index 79eee0e..bd450f0 100644 --- a/docs/specifications/collection-views.md +++ b/docs/specifications/collection-views.md @@ -48,7 +48,7 @@ Each is a standalone component (`src/lib/components/*CollectionView.svelte`) tak **Table** (`TableCollectionView.svelte`) — a grid over `visibleProperties(schema, config)` columns and `projectRecords(rows, config)` rows, each cell a `PropertyValueCell.svelte` (the same property-type-to-editor mapping every view shares — text/number/date/select/checkbox/relation, one implementation, not reimplemented per view). Each column header carries its own `FieldMenu.svelte` (rename/change type, insert left/right, duplicate, hide in this view, delete — see §7), and `ViewToolbar` exposes a "Manage fields" entry point (`FieldManagerDialog.svelte`) shared by Table, Board, and Calendar alike. It takes a `variant: 'embedded' | 'full-page'` prop (default `'embedded'`): embedded mode shows a small link to `/table/[id]` when the collection has no properties yet (creating the very first field needs a real row/column grid to attach to) and an "Open full table →" footer link; `/table/[id]` itself renders this same component with `variant="full-page"`, which suppresses both (a self-link to the page already showing would be nonsensical) and always renders the grid, even with zero fields, matching the full-page route's pre-#189 behavior. Two callback props, `onConnect`/`onSnapshot`, let a composing parent (only `/table/[id]` today) observe the component's own resolved `Y.Doc` and collection snapshot — e.g. for its own title-editing input — without maintaining a second, redundant `useCollectionConnection`/`useCollectionView` pair alongside the one this component already owns. -**Board** (`BoardCollectionView.svelte`) — `config.groupBy` names the `select` property driving columns; a dropdown lets the user switch it if the schema has more than one, or the collection gets prompted to add one (`appendCollectionField`) if it has none, rather than silently rendering nothing. Cards show the record's first `text`-type property as a directly-editable title, plus whatever other schema properties are in `visibleProperties`. Moving a card between columns — via native HTML5 drag-and-drop, a card's own "Move to" `` (a keyboard/screen-reader-accessible alternative to drag-and-drop), or the card's own field editor if the grouping property is also a visible field — updates that record's existing grouping property (`setCollectionCell`); it is never a reorder of Collection membership or a record copy. Manual sort mode (`config.sort.mode === 'manual'`, the default) additionally supports dragging a card to a specific position within a column — that ordering is **session-local component state** (`manualOrder: Record`), not written to the block or the Collection (see §8 for why manual order specifically stays ephemeral even though the rest of `viewConfig` is now a draft that gets explicitly saved — §9). **Board swimlanes** (`config.swimlaneBy`, issue #67/#165) — an optional second grouping dimension, rows crossing the existing columns, GitLab/Jira-style. A "Swimlane by" dropdown next to "Group by" offers every `select` property in the schema except whichever one is already `groupBy` (Board never lets one property drive both dimensions); it's hidden entirely when no other `select` property exists, rather than prompting to create one the way the initial "Group by" empty state does — the swimlane dimension is optional, not required to use Board at all. When set, `groupBySwimlaneAndColumn` (§3) replaces the flat column render with one row per swimlane (plus a trailing "No ``" catch-all), each rendering the same full column set — every column still appears inside every swimlane, including an otherwise-empty one, matching `groupBySelectProperty`'s own "preserve empty groups" rule at the second dimension. Both the "Move to column" `` are per-card, keyboard/screen-reader-accessible alternatives to drag-and-drop; dragging a card into a different swimlane's column cell (or dropping it via native drag-and-drop) sets both the column and swimlane grouping properties in one `setCollectionCell` call, and the swimlane-only move sets just the swimlane property, leaving the record's column untouched. Manual per-cell card order (§8) is scoped by swimlane too, keyed by `` `${groupBy}:${swimlaneBy}:${swimlaneOptionId}:${columnOptionId}` `` — switching either grouping property, or moving a card across swimlanes, never resurrects a stale order saved under a different cell. Retargeting "Group by" onto the property currently driving swimlanes clears `swimlaneBy` (rather than leaving it pointing at what's now the column property too — a duplicate-dimension state `swimlaneCandidates` can never resolve back to) as part of that same `onConfigChange` call; a different retarget leaves `swimlaneBy` untouched. If `swimlaneBy` ever names a property that's been deleted out from under it instead, the swimlane row disappears and Board falls back to its flat single-dimension column view — the same graceful-degradation `groupProperty` itself already has when `groupBy` names a missing field. @@ -86,6 +86,7 @@ Board and Calendar cards used to title themselves off "the first `text`-type fie - **Resolution: `resolvePrimaryField(schema, primaryFieldKey)`.** Returns the schema field named by `primaryFieldKey` when it exists and is an eligible type; otherwise falls back to the first `text` field in schema order — the exact pre-#96 implicit rule — so a Collection created before this feature, or one whose chosen primary field was since deleted or retyped away, keeps showing the same title without a migration step. The fallback only ever considers `text` fields, not every eligible type, so this returns `undefined` both when the schema has no eligible field at all and when it has eligible fields (e.g. a lone `number` or `select` field) but none of type `text` (Board/Calendar's card/entry title then reads "Untitled", same as before). Board and Calendar both call this in place of their old `schema.find(p => p.type === 'text')` line; `FieldMenu`'s primary-field indicator (next bullet) calls it too, so the star always matches what's actually titling a card. - **Eligible types: everything except `relation`.** A `relation` value is a list of record IDs with no display string of its own (data-model.md's `PropertyValue`) — every other type (`text`, `number`, `date`, `select`, `checkbox`) already renders as one displayable value via `PropertyValueCell`, so `setPrimaryField` rejects choosing a `relation` field (`ValidationError`), and a retype that turns the current primary field _into_ `relation` clears `primaryFieldKey` in the same transaction rather than leaving it pointing at a now-invalid field. Deleting the current primary field clears it the same way. Both repairs mirror the existing `deleteCollectionProperty`/`updateCollectionProperty` pattern of fixing up stale schema references in one transaction rather than leaving a dangling key for a reader to notice later. - **Display value: `primaryFieldDisplayValue(value, property)`** (`src/lib/data/views.ts`) — a plain-text rendering of any eligible type's value (a `select` value resolves through `property.options` to its label; `checkbox` renders "Checked"/empty), used for Board/Calendar's card-title `aria-label`s and Calendar's static entry-title text. Board's own card _editor_ still renders the primary field as a full `PropertyValueCell` (so whichever type is chosen — not just `text` — stays directly editable inline); this function is only for the places a plain string is needed instead of an editable cell. +- **Colliding with `groupBy` or `swimlaneBy` (issue #104).** `primaryFieldKey` and Board/Calendar's `groupBy` (§3), or Board's `swimlaneBy`, are independent choices — nothing stops a user from pointing any of them at the same `select`/`date` field. Calendar's entry title is _always_ the plain, non-editable `primaryFieldDisplayValue` text described above (it never renders the title as a `PropertyValueCell` at all), so this collision doesn't create a duplicate control there. Board's card title normally _is_ a directly-editable `PropertyValueCell` (previous bullet), which — when `titleProperty.key` matches either `groupProperty.key` or `swimlaneProperty.key` — would otherwise sit right above the matching "Move to column"/"Move to swimlane" `