Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
3f80849
docs: Stage-3 Sort Work + workspace UI design spec [CLUE-610]
scytacki Jul 27, 2026
93d4af6
feat: scope guards for group and class+unit scoped documents [CLUE-610]
scytacki Jul 27, 2026
0271579
feat: state the class+unit scope's absent curriculum fields explicitl…
scytacki Jul 27, 2026
46435fc
feat: backfill class-wide curriculum scope; scope-select both axes pa…
scytacki Jul 27, 2026
229c0cd
feat: section class-wide documents under Whole Class, ordered by scop…
scytacki Jul 27, 2026
84a87c5
feat: file class-wide documents under No Name in the by-name sort [CL…
scytacki Jul 27, 2026
47345e3
feat: fetch unit-scoped documents under the investigation and problem…
scytacki Jul 27, 2026
d56dcd6
fix: reword comments to describe live constraint, not history [CLUE-610]
scytacki Jul 28, 2026
a1aae2a
feat: one edit predicate for Sort Work and the resources pane [CLUE-610]
scytacki Jul 28, 2026
775e463
fix: exclude published documents and researchers from canUserEditDocu…
scytacki Jul 28, 2026
4d287d5
feat: authorize concurrent-document history writes by class membershi…
scytacki Jul 28, 2026
eae0efd
fix: gate concurrent's write path so it cannot forge history-write au…
scytacki Jul 28, 2026
e65df98
docs: record Stage-3 axis progress and verification results [CLUE-610]
scytacki Jul 28, 2026
caef08a
fix: guard group-document title on groupId, correct exemplar scope do…
scytacki Jul 28, 2026
7a2eb96
feat: title a class-wide document seen from another unit [CLUE-610]
scytacki Jul 28, 2026
bb38e10
docs: record the constraint dynamic kinds place on scope [CLUE-610]
scytacki Jul 28, 2026
d14a5fb
docs: note that the group-title check is transitional [CLUE-610]
scytacki Jul 28, 2026
7e1e53c
refactor: name the scope guards for the dimension they read [CLUE-610]
scytacki Jul 28, 2026
63a16b6
docs: record what the owner axis has to support [CLUE-610]
scytacki Jul 29, 2026
07f1835
fix: decide group membership by owner, not by group id [CLUE-610]
scytacki Jul 29, 2026
c04b36a
docs: split the scope axis into container and curriculum [CLUE-610]
scytacki Jul 29, 2026
98f065e
docs: restate the owner requirements as behavior, not implementation …
scytacki Jul 29, 2026
e61a627
docs: move the axes reading notes out of document-scope.md [CLUE-610]
scytacki Jul 29, 2026
f0ee513
refactor: split the scope axis in code into owner, container, and cur…
scytacki Jul 29, 2026
d991b10
refactor: canUserEditDocument chooses one metadata source instead of …
scytacki Jul 29, 2026
5abb788
refactor: address canonical slots as container + owner + label [CLUE-…
scytacki Jul 29, 2026
a580486
fix: resolve a group document's members by owner, not by bare group i…
scytacki Jul 30, 2026
be8b4ad
refactor: read a group owner off the document's uid, like a class own…
scytacki Jul 30, 2026
35fdfee
docs: record the edit gate as a worked example of the permissions axi…
scytacki Jul 30, 2026
fd586ff
refactor: type the by-group section pair instead of casting each sort…
scytacki Jul 30, 2026
ae0fe80
refactor: name Sort Work's extra listener for the whole unit, not "un…
scytacki Jul 30, 2026
e4c7d4a
docs: point DocumentModel's group comments at the axis guards [CLUE-610]
scytacki Jul 30, 2026
970320e
Merge branch 'CLUE-550-stage-3a-axes-presentation' into CLUE-550-stag…
scytacki Jul 31, 2026
43a171e
docs: record that Stage 3 shipped as two PRs [CLUE-610]
scytacki Jul 31, 2026
7a33a7a
Merge branch 'CLUE-550-stage-3a-axes-presentation' into CLUE-550-stag…
scytacki Jul 31, 2026
55fb37e
Merge branch 'CLUE-550-stage-3a-axes-presentation' into CLUE-550-stag…
scytacki Aug 4, 2026
6d60134
docs: describe the owner axis as the code implements it [CLUE-610]
scytacki Aug 4, 2026
d9d5324
docs: state the dynamic-kind bound on the curriculum axis [CLUE-610]
scytacki Aug 4, 2026
eba6498
docs: explain a unit-declared title once, where the field is defined …
scytacki Aug 4, 2026
30d5884
docs: drop a stale account of what DocumentModel.groupId holds [CLUE-…
scytacki Aug 4, 2026
d6a7aa7
fix: allow only setting concurrent, and require the offering with gro…
scytacki Aug 4, 2026
32331ad
docs: attribute this branch's roadmap rows to its own story [CLUE-610]
scytacki Aug 6, 2026
c5cd8c9
docs: name the design spec for the story that carries it [CLUE-610]
scytacki Aug 6, 2026
6f139bd
fix: sort only all-digit group ids numerically [CLUE-610]
scytacki Aug 6, 2026
61a310d
fix: name a document whose owner is not a class member by its title a…
scytacki Aug 7, 2026
a4c4928
Merge branch 'CLUE-610-sort-work-test-robustness' into CLUE-610-class…
scytacki Aug 11, 2026
018a5b2
refactor: read a kind definition through one scoped lookup [CLUE-610]
scytacki Aug 14, 2026
41c5834
refactor: mint the class owner uid where the guard reads it back [CLU…
scytacki Aug 18, 2026
8f73737
refactor: take the group id itself rather than a context holding one …
scytacki Aug 18, 2026
fbfd132
refactor: answer the remaining review comments [CLUE-610]
scytacki Aug 18, 2026
aeaca0c
docs: say what constrains a kind, not that a slot label is one [CLUE-…
scytacki Aug 18, 2026
d664f40
docs: show what copy and publish templates actually produce [CLUE-610]
scytacki Aug 18, 2026
6ef63eb
docs: say that canonical names a slot rather than answering yes/no [C…
scytacki Aug 18, 2026
ed3c2cb
docs: lead the canonical slot argument with problem and planning [CLU…
scytacki Aug 18, 2026
d531f8f
docs: say the researcher edit rule is about other people's documents …
scytacki Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions docs/document-axes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ place, with `type`/`kind` dereferenced only inside a kind registry, a creation f

- **Concepts — what the axes are, read out of current CLUE behavior:** [axes.md](./axes.md)
- **Target — how the axes live in code (layers and boundaries):** [target-architecture.md](./target-architecture.md)
- **Current state — what a consumer can read off a document today:** [reading-axes-in-code.md](./reading-axes-in-code.md)
- **Research background (current-state evidence):** the findings doc, on the `document-type-decomposition`
branch (~49KB; left there rather than imported).

### Related existing docs this roadmap evolves toward

- [../document-types.md](../document-types.md) — the current `type` catalog these axes decompose.
- [../document-scope.md](../document-scope.md) — the current scoping model the `scope` axis formalizes.
- [../group-docs/README.md](../group-docs/README.md) — the group-document feature; its concurrency behavior is
the first thing rebased onto the `concurrent` axis.

Expand All @@ -26,15 +26,16 @@ flips the rows it delivers **in the same PR**, and names the stage/ticket under

| Axis / component | Mechanism (target) | Status | Delivered by |
|---|---|---|---|
| `canonical` (single pointed-to doc for a scope slot) | scoped pointer slots, rule-enforced | done | CLUE-524; class+unit pointer scope added CLUE-550 Stage 2 |
| `canonical` (single pointed-to doc for a slot) | pointer slots addressed as container + owner + label, rule-enforced | done | CLUE-524; class+unit slots added CLUE-550 Stage 2; CLUE-610 gave every slot an explicit owner segment, taken from the document's `uid` |
| `concurrent` (multi-writer vs single-writer) | stored per-doc; rule-readable; `DocumentModel` prop sourced from Firestore at open | done | CLUE-550 Stage 1 |
| `kind` (preset/cohort tag: defaults, presentation, templates) | stored per-doc tag; dereferenced only in the kind registry | in progress | CLUE-550 Stage 1 (stored + registry seeded; presentation wiring lands Stage 3); class-wide slot kinds registered and their titles resolved by kind (`getDocumentTitle`) CLUE-550 Stage 2 |
| `owner` (authoring identity / provenance) | creation: kind-declared `ownerType` → owner `uid` (in the kind registry); read: getter over stored `uid` | in progress | CLUE-550 Stage 2 (creation-side owner derivation registry-declared for all kinds via `getDocumentOwner`; read-side getter still to come) |
| `scope` (org + curriculum association refs) | creation: `getDocumentScopeFields(kind, ctx)` stamps a kind's association fields, keyed on a registered `scopeType`; read: consumers read the individual scope fields, narrowing with field/axis **guards** (e.g. `hasOfferingScope`) rather than branching on `type` — whether to also add a single unified `scope` getter is an open question | in progress | CLUE-550 Stage 2 (creation-side scope fields registry-derived for every kind — the `createFirestoreMetadataDocument` type switch is gone) |
| `permissions` (composed grant set) | permission-policy grants (referenced policy) + stored per-doc grants | not started | — |
| `kind` (preset/cohort tag: defaults, presentation, templates) | stored per-doc tag; dereferenced only in the kind registry | done | CLUE-550 Stage 1 (stored + registry seeded); titles resolved by kind Stage 2; presentation wired by CLUE-610 (workspace title bar reads the registry; no consumer branches on kind); CLUE-610 also scopes a unit-declared kind's definition to its unit — see "Static and dynamic kinds" in [axes.md](./axes.md) |
| `owner` (who the document belongs to) | creation: kind-declared `ownerType` → owner `uid` (`getDocumentOwner`) plus a group owner's stored `groupId` (`getDocumentOwnerFields`); read: `hasGroupOwner(doc)` and `hasClassOwner(doc)`, each over the uid prefix its minter used | in progress | CLUE-550 Stage 2 (creation-side owner derivation registry-declared for all kinds); CLUE-610 (both guards; `groupId` moved onto this axis, off the container, and then off the owner *question* — the `uid` is the sole authority). Still to come: the user level, and a getter that returns which owner a document has rather than testing for one |
| `container` (where the document is kept: class → classUnit → offering) | creation: kind-declared `containerType`, stamped by `getDocumentLocationFields(kind, ctx)`; read: `isInClassUnitContainer(doc)`, over an `offeringId` now surfaced on both metadata types | in progress | CLUE-550 Stage 2 (creation side, every kind); CLUE-610 (`containerType` replaces `scopeType`, with no group level — a group document is kept in the offering and owned by the group; the edit gate switched from a curriculum test to this one). Still to come: a guard for the class level, and a getter returning the container |
| `curriculum` (what the document is about: none → unit → investigation → problem) | creation: fixed by the kind's `containerType`, since every container above the class is identified by a curriculum coordinate; read: `getCurriculumLabel(doc)` | in progress | CLUE-550 Stage 2 (creation side, every kind); CLUE-610 (the label, and the unit level states its absent fields explicitly so it is queryable). No consumer asks a yes/no curriculum question, so no guard exists |
| `permissions` (composed grant set) | permission-policy grants (referenced policy) + stored per-doc grants | not started | Consumers are spread widely and each composes the answer by hand; the edit gate `canUserEditDocument` is called out as a worked example, including its `type` branch for published documents. See "Not covered yet" in [reading-axes-in-code.md](./reading-axes-in-code.md) for what a named policy would replace |
| kind registry (by-kind view) | `register`/`get` map keyed on `kind`; `fn(doc)` API | done | CLUE-550 Stage 1 |
| behavior modules (by-behavior view) | `fn(doc)` reading axis getters / registry; never branch on `kind` | in progress | CLUE-550 Stage 1 (history + write-sync on concurrent; read-access + rules-delete on group type, interim until the permissions axis) |
| creation factory (the one `kind → axis` bridge) | reads registry defaults, stamps axis values on a new doc | in progress | CLUE-550 Stage 2 (per-slot class-wide canonical creation; owner `uid` and scope fields stamped from the kind's `ownerType`/`scopeType`) |
| behavior modules (by-behavior view) | `fn(doc)` reading axis getters / registry; never branch on `kind` | in progress | CLUE-550 Stage 1 (history + write-sync on concurrent; read-access + rules-delete on group type, interim until the permissions axis); CLUE-550 Stage 3a (collaborative thumbnail treatment reads `concurrent`); CLUE-610 (edit gate `canUserEditDocument` and the collaborative title bar read `concurrent`) |
| creation factory (the one `kind → axis` bridge) | reads registry defaults, stamps axis values on a new doc | in progress | CLUE-550 Stage 2 (per-slot class-wide canonical creation; owner and location fields stamped from the kind's `ownerType`/`containerType`) |

Status values: `not started` / `in progress` / `done`.

Expand All @@ -44,15 +45,28 @@ CLUE-550 ("class-wide collaborative documents") is the first concrete slice of t
`concurrent` and `kind` stored axes plus a kind registry, then rebased group-document behavior (concurrent
history, non-owner write-sync, class-wide read access, the rules delete clause) from `type === "group"` onto
the stored `concurrent`. Stage 2 auto-creates class-wide documents (e.g. the driving-question board) via the
canonical-pointer engine: a class+unit pointer scope alongside the existing offering+group scope, with
canonical-pointer engine: a class+unit slot alongside the existing offering+group one, with
get-or-create convergence guaranteeing exactly one document per slot per class. Stage 2 also begins the
`owner` and `scope` axes on the creation side, now for **every** kind: a document's owner `uid` is derived from
the kind's registered `ownerType` (`user` / `group` / `class`) — class-wide documents owned by a class-scoped
synthetic uid (`class_<classHash>`) — and its scope association fields from the kind's registered `scopeType`
via `getDocumentScopeFields(kind, ctx)`, both resolved in the kind registry rather than a `type` switch. Because
all kinds are registered, `createFirestoreMetadataDocument` derives owner and scope through these registry calls
`owner`, `container`, and `curriculum` axes on the creation side, now for **every** kind: a document's owner
`uid` is derived from the kind's registered `ownerType` (`user` / `group` / `class`) — class-wide documents
owned by a synthetic class uid (`class_<classHash>`) — and the fields saying where it is kept and what it is
about from the kind's registered container, both resolved in the kind registry rather than a `type` switch.
Because all kinds are registered, `createFirestoreMetadataDocument` derives all of these through registry calls
for all document types. The kind axis fields (`kind`/`concurrent`) are stamped only on
`type:"group"` documents — avoiding a stamp we would have to migrate if the publication kinds are later folded
into the kinds they publish. That gate widens as each type is converted; see
[Which documents get stamped](./target-architecture.md#which-documents-get-stamped--a-gate-that-narrows-as-types-are-converted)
for how it is retired.

CLUE-610 surfaces those documents: Sort Work sections them under "Whole Class" by owner and curriculum
rather than by type, a whole-unit listener keeps them visible under the investigation and problem
filters, presentation reads `concurrent` and the kind registry, and one predicate
(`canUserEditDocument`) gates every Edit button. It also settles how these axes are modeled in code:
consumers read narrow named guards over the stored fields, with no level enum and no unified struct;
a kind declares `ownerType` and `containerType`; and `groupId` sits on the owner axis, so there is no
group container level (see [reading-axes-in-code.md](./reading-axes-in-code.md)). Canonical slots follow:
each is addressed as its container plus its owner plus a label, with the owner segment read straight from
the document's `uid` so the pointer path no longer depends on `groupId` at all. Both owner guards read the
`uid` the same way, which leaves it the single authority on who a document belongs to; the stored
`groupId` is left carrying only Sort Work's group label and the queries that retire with the
canonical-pointer migration.
Loading
Loading