diff --git a/docs/specifications/collection-views.md b/docs/specifications/collection-views.md index 035c858..4c5d527 100644 --- a/docs/specifications/collection-views.md +++ b/docs/specifications/collection-views.md @@ -52,7 +52,7 @@ Each is a standalone component (`src/lib/components/*CollectionView.svelte`) tak **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. -**Calendar** (`CalendarCollectionView.svelte`) — `config.groupBy` names the `date` property driving placement, same add-one-if-missing prompt as Board. A fixed 6-row/42-cell month grid (leading/trailing days from adjacent months included, so the grid's shape doesn't jump between 5- and 6-row months) places each record on the day matching `dateKeyForRecord`; a record with no value for the date property renders in an "Unscheduled" section below rather than being hidden. Every entry (scheduled or unscheduled) shows the Collection's resolved primary field (§7) as its title — normally a directly-editable `PropertyValueCell`, matching Board, rendered `compact` given Calendar's denser rows — plus its date property (also inline via `PropertyValueCell`, so rescheduling, including giving an unscheduled record its first date, is a direct edit) and whatever other schema properties are in `visibleProperties` (issue #105: this predates #96 as a plain-text-only title and was made editable to match Board once "primary field" became a named concept applied identically everywhere else — Calendar's entries already supported inline editing for the date field, so this isn't a new interaction pattern for the view, just extending it to one more field). A day cell's "+" button creates a record with that day pre-filled (`createCollectionRow`). +**Calendar** (`CalendarCollectionView.svelte`) — `config.groupBy` names the `date` property driving placement, same add-one-if-missing prompt as Board. A fixed 6-row/42-cell month grid (leading/trailing days from adjacent months included, so the grid's shape doesn't jump between 5- and 6-row months) places each record on the day matching `dateKeyForRecord`; a record with no value for the date property renders in an "Unscheduled" section below rather than being hidden. Every entry (scheduled or unscheduled) renders its date property inline via `PropertyValueCell`, so rescheduling — including giving an unscheduled record its first date — is a direct edit. A day cell's "+" button creates a record with that day pre-filled (`createCollectionRow`). ## 5. Permission scoping and `get_document` @@ -64,6 +64,8 @@ Each is a standalone component (`src/lib/components/*CollectionView.svelte`) tak Both new components call `src/lib/data/records.ts` directly against the browser's own `Y.Doc`, the same direct-UI-mutation pattern every other schema edit in this codebase uses (`updateCollectionSchema` from `+page.svelte` predates this feature) — not routed through the service layer, and picked up generically by `attachDocAuditObserver` (`audit-coverage.md`) like any other direct UI write. +**Field-label contract.** Every field label is trimmed before storage, must be non-blank, and must be unique case-insensitively within its Collection schema. The Add field form, Board's initial select-property prompt, Calendar's initial date-property prompt, and FieldMenu rename all surface a rejected label as an inline `ValidationError`; no schema or view configuration is written on rejection. FieldMenu's generated actions choose an available label (`New field`, then `New field 2`, and `