Skip to content

CLUE-550 Stage 2: Class-wide slots + get-or-create via pointer - #2928

Merged
scytacki merged 36 commits into
masterfrom
CLUE-550-class-wide-slots
Aug 4, 2026
Merged

CLUE-550 Stage 2: Class-wide slots + get-or-create via pointer#2928
scytacki merged 36 commits into
masterfrom
CLUE-550-class-wide-slots

Conversation

@scytacki

@scytacki scytacki commented Jul 24, 2026

Copy link
Copy Markdown
Member

CLUE-550 Stage 2 — Class-wide slots + get-or-create via pointer

Stage 2 of the class-wide collaborative documents work. It makes the app auto-create a class-wide collaborative document — the Driving Question Board (DQB) is the default slot — exactly once per class per unit, server-race-free, reusing the CLUE-524 canonical-pointer engine. There is no UI yet (Sort Work sectioning, titling, presentation, and editing are Stage 3); this PR creates the documents but shows nothing.

Builds on the concurrent/kind axes and kind registry from Stage 1 (base branch CLUE-550-concurrent-kind-axes). The target architecture and per-axis status live in docs/document-axes/README.md (this PR flips the roadmap's creation-factory row to in progress and extends the canonical row to class+unit).

How a class-wide document is modeled

It is the same generic collaborative document as a group doc, differentiated only by orthogonal fields — no new document type:

Concern Value
Behavior (concurrent history, non-owner editable) concurrent: true (Stage-1 axis)
Identity kind (e.g. drivingQuestionBoard), registered with metadataFields: { concurrent: true }
Scope context_id (classHash) + unit; no offeringId/groupId
Pointer slot canonical: <kind> (label = kind)
Legacy type tag type: "group" (transitional, Stages 1–3)
RTDB owner class-scoped synthetic uid class_<classHash> (mirrors group docs' group_<offeringId>_<groupId>)

Because it carries concurrent: true and type: "group", Stage-1's behavior wiring (concurrent history manager, non-owner write-sync, class-wide read) already applies to it with no new branches.

What's in the PR

  1. classWideDocuments unit config — an authored array of { kind, title } slots on UnitConfiguration, read as stores.appConfig.classWideDocuments. This is the correct home for the authored DQB title.
  2. Field-based canonical pointer builder — replaces the group-specific getGroupCanonicalPointerPath with one getCanonicalPointerPath(scope, label) that derives the path from the scope fields present: canonical/v1/classes/<classHash> / (offerings/<offeringId> | units/<unit>) / [groups/<groupId>] / slots/<label>. The units/ segment is used only when there is no offering, so the group pointer path is byte-identical (no migration). A future problem/planning pointer (offering + owner) slots in as a users/<owner> segment — documented, not built.
  3. firestore.rules class+unit pointercanonicalPointerPath rewritten to build the same path field-by-field (path() + string concat), gated by hasCanonicalScope (a non-empty offering or unit field — a field written as null counts as absent scope); plus a new immutable pointer write-guard match block canonical/v1/classes/{classId}/units/{unit}/slots/{label}.
  4. Kind-registry-driven creation — a class-wide document is created with createDocument({ type: "group", kind }); its registered ownerType: "class" / scopeType: "classUnit" drive the rest. getDocumentOwner returns the synthetic class owner class_<classHash>, getDocumentScopeFields stamps context_id + unit (no offeringId/groupId), and getDocumentKindMetadataFields(kind) stamps kind + concurrent — so the document is stamped correctly with no class-wide-specific branch, and the kind (e.g. drivingQuestionBoard) is not mis-stamped as "group".
  5. getOrCreateClassWideDocument({ kind, title }) — builds the class+unit pointer path with the slot's kind as the label and delegates to the existing race-safe getOrCreateCanonicalDocument.
  6. Auto-creation on unit opencreateDeclaredClassWideDocuments() registers each declared slot's kind and fires one getOrCreateClassWideDocument per slot inside DB.connect()'s unitLoadedPromise.then block, fire-and-forget (the pointer transaction converges all class members to one doc per slot). A malformed or duplicate kind throws in registerDocumentKind and is caught and skipped, so one bad author entry never blocks the others.
  7. Verification + roadmap — emulator tests for two-client pointer convergence and the review-issue-Curriculum/Investigation/Problem/Section models #6 ordering, plus the document-axes roadmap status flip.

Notes for reviewers

  • No data migration. Class-wide documents are unreleased.
  • Security surface is small: no RTDB rules change (the class subtree is already class-member-writable); no Firestore create/update change beyond the pointer path (a class member can already create a doc with their own context_id; the create carries no canonical, which is granted only by the pointer-claim update).
  • Review issue Curriculum/Investigation/Problem/Section models #6 (drift-guard weakening) is a verified no-op — the history.length === 0 weakening lived only in the superseded original DQB PR and is in neither this branch nor master. The metadata-before-history ordering is structurally guaranteed because getOrCreateCanonicalDocument writes the Firestore metadata before the document is opened; the emulator test verifies this rather than changing drift-guard code.
  • Dormant for any unit that declares no classWideDocuments. The demo/units/qa unit now declares one drivingQuestionBoard slot so the create/pointer flow can be exercised end-to-end (see Testing); production curriculum units declare none.
  • Two things flagged for the Stage-3 plan (not issues here): the eager open of each slot doc on unit load (cost + ensuring a type:"group" doc with no offering/group doesn't leak into existing type-based enumerations), and confirming the concurrent history-write path is authorized for the class_<classHash> synthetic owner when editing is wired.

Testing

  • Unit (Jest): config resolution; the field-based pointer builder (group byte-identical + class+unit); class+unit creation stamping (scope, kind, concurrent; no offeringId/groupId/canonical); getOrCreateClassWideDocument fast/create paths; per-slot auto-creation.
  • Rules (emulator, canonical-pointers-rules): class-member create/read of the class+unit pointer; immutability; cross-class denial; canonical-claim-confirmed-by-pointer; two-client convergence; Curriculum/Investigation/Problem/Section models #6 readability; plus a class-scoped (unit: null) doc cannot claim canonical. 25/25 green.
  • Whole-repo check:types and lint:build clean.
  • Manual end-to-end: the demo/units/qa unit declares a drivingQuestionBoard class-wide slot (src/public/demo/units/qa/content.json). Loading that unit auto-creates one class-wide document per class+unit and claims its canonical pointer, so the Firestore metadata document (owner class_<classHash>, unit, kind: "drivingQuestionBoard", concurrent: true) and the classes/<classHash>/units/<unit>/slots/drivingQuestionBoard pointer can be inspected in the emulator/console.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.33333% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.05%. Comparing base (11f18e7) to head (15c5f57).

Files with missing lines Patch % Lines
src/lib/db.ts 92.68% 6 Missing ⚠️
src/models/document/document-kinds.ts 98.03% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2928      +/-   ##
==========================================
+ Coverage   86.02%   86.05%   +0.02%     
==========================================
  Files         977      977              
  Lines       55660    55742      +82     
  Branches    14671    14692      +21     
==========================================
+ Hits        47884    47971      +87     
+ Misses       7758     7753       -5     
  Partials       18       18              
Flag Coverage Δ
cypress ?
cypress-regression 71.84% <81.37%> (+0.24%) ⬆️
cypress-smoke 41.66% <78.62%> (+0.36%) ⬆️
jest 56.56% <70.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cypress

cypress Bot commented Jul 24, 2026

Copy link
Copy Markdown

collaborative-learning    Run #19702

Run Properties:  status check passed Passed #19702  •  git commit 15c5f57d20: Merge remote-tracking branch 'origin/master' into CLUE-550-class-wide-slots
Project collaborative-learning
Branch Review CLUE-550-class-wide-slots
Run status status check passed Passed #19702
Run duration 10m 23s
Commit git commit 15c5f57d20: Merge remote-tracking branch 'origin/master' into CLUE-550-class-wide-slots
Committer Scott Cytacki
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 1
Tests that did not run due to a developer annotating a test with .skip  Pending 5
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 220
View all changes introduced in this branch ↗︎

The document-axes roadmap tracks the axis architecture; the #6 first-session
drift finding belongs in the CLUE-550 design specs, which carry it. Also drop
the redundant row-flip sentence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CLUE-550]

Move canonical-document pointers out of the interleaved classes/<id>/... tree
into a top-level, versioned `canonical/v1/classes/.../slots/<label>` collection.
Because pointers no longer share paths with class/offering/group data, the rules
state the uniform concerns once on a recursive-wildcard match (class-member read;
immutable) and keep only create per-scope (each scope authorizes its own slot
claim; Firestore ORs allow-rules, so a single wildcard create couldn't be
tightened per-scope). The `v1` segment versions the layout and restores even
path depth. No migration: pointers exist only for unreleased group docs, and an
orphaned old-path pointer falls back to the existing no-pointer path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scytacki and others added 12 commits July 24, 2026 08:42
Group the three canonical-pointer match blocks under a single
match /canonical/v1/classes/{classId} so classId is captured once and
shared. Inline the single-use canonicalPointerReadable helper into the
wildcard read rule and canonicalPointerCreatable. Rule semantics are
unchanged; the emulator canonical-pointers-rules suite passes 24/24.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
]

Add a target-architecture section deciding how document metadata types
evolve once `type` is decomposed into axes: the kind-discriminated
DBDocumentMetadata union dissolves into a generic base plus structural
requirement types narrowed by axis-field type guards (e.g.
hasOfferingScope), never by `type` — the type-system form of the core
"read axes, not kind" rule. Notes that DBGroupDocMetadata and
DBClassWideDocMetadata already share `type: "group"` with different
shapes, so `type` already fails to discriminate shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hoist `kind` out of the classWide bundle into an explicit parameter on
createDocument, getOrCreateCanonicalDocument, and
createFirestoreMetadataDocument (defaulting to `type`, since every
non-class-wide kind equals its type). createFirestoreMetadataDocument
now always stamps getDocumentKindMetadataFields(kind) instead of
reconstructing it from classWide?.kind ?? metadata.type.

Two simplifications fall out: IClassWideCreateInfo drops `kind` (keeping
only unit/syntheticUid), and createFirestoreMetadataDocument drops its
classWide object param for a bare `unit?`, keying the class+unit scope
branch on unit != null. Group documents now pass kind === type
explicitly, which is where type and kind will diverge once `type`
decomposes to a generic value.

check:types clean; db.test.ts 36/36; eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fold pointerPath/type/kind into IGetOrCreateCanonicalDocumentOpts so the
method takes one options object instead of mixing positional args with an
opts bag. Move class-wide kind registration from getOrCreateClassWideDocument
into createDeclaredClassWideDocuments, so registration lives in the
declaring function rather than the get-or-create path.

check:types clean; db.test.ts 36/36; eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the owner (authoring identity, stored as `uid`) derivation into the
kind registry: kinds declare an ownerScope (user | group | class) and
getDocumentOwner(kind, ctx) in document-kinds maps it to the concrete uid
from runtime context, replacing the type switch in createDocument. This is
the first slice of the `owner` axis living in one place; the document-axes
roadmap marks the axis in progress.

Make the class-wide owner the class (class_<classHash>), shared across all
of a class's units, instead of class_<unitCode>. The unit belongs to the
document's canonical slot, not its ownership -- mirroring how a group
document's owner is group_<off>_<grp>. Reads rebuild the RTDB path from the
stored metadata uid and documents are located by canonical pointer, so the
owner value is not load-bearing and no migration is needed.

Decouple the pointer createdBy from the owner: it now records the real
user.id of whoever won the creation race (or backfilled a legacy pointer),
added provenance. Orphan cleanup reads the owner back from the created
metadata's uid. With createdBy and the owner both sourced elsewhere, the
groupUserId opt and the classWide syntheticUid field drop out, and
createDocument passes `owner` straight to the storage-path helpers
(getUserPath falls back to user.id), removing the syntheticUserId variable.

check:types clean; lint clean; document-kinds + db suites 45/45;
emulator canonical-pointers-rules 24/24.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move a group-typed document's scope association fields into the kind layer:
getDocumentScopeFields(kind, ctx) in document-kinds returns { groupId } for
group scope and { unit } for class scope (keyed on the owner scope, which is
unambiguous for these two fields). This is the first slice of the `scope`
axis; other kinds' scope fields stay on the createDocument type switch.

With scope (and owner) now kind-driven, createDocument no longer needs the
classWide descriptor: for a type:"group" document it distinguishes the group
doc from a class-wide doc by the kind's registered scope. IClassWideCreateInfo
is deleted and getOrCreateCanonicalDocument carries a plain title.

getDocumentScopeFields is called inside createFirestoreMetadataDocument, which
reads the runtime values from the stores, so groupId/unit are no longer passed
as parameters. The context-validity check the inline group reject provided is
preserved as an explicit validateDocumentKindCreation(kind) in db.ts (it has
the stores access the registry lacks), which throws before anything is written.

Stamped fields are unchanged on every path (group / class-wide / personal /
problem); the refactor only relocates where they are derived. The document-axes
roadmap marks the scope axis in progress.

check:types clean; lint clean; document-kinds + db suites 48/48.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Stop returning the RTDB metadata object from createDocument. It is only
  used internally (written to RTDB, read back for the Firestore doc); no
  caller consumed the returned copy. The two publish methods that forwarded
  it now resolve { document } only (their sole consumer ignored the value),
  and the now-unused DBPublicationDocumentMetadata import is dropped.
- Rename the internal metadata local to rtdbMetadata for clarity.
- Deduplicate the common metadata fields via `const common = {...} as const`
  (as const preserves the version "1.0" literal so the discriminated-union
  members still match); `type` stays a fresh literal per branch.
- Add a default branch that throws for unhandled document types (e.g. the
  deprecated "section"). The switch is not exhaustive, and because
  rtdbMetadata is read inside a .then() closure TypeScript's used-before-
  assigned analysis can't see the gap, so guard it explicitly rather than
  silently set(undefined).
- Note the write-only, problem-family-only top-level classHash on the
  problem metadata and in metadata-fields.md.

check:types clean; lint clean; db suite 36/36.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ds explicitly [CLUE-550]

Group and class-wide documents now store only base RTDB metadata (version/self/
createdAt/type); their scope, owner, title, and kind are stamped into the Firestore
metadata by createFirestoreMetadataDocument. DBClassWideDocMetadata is removed and
DBGroupDocMetadata reduced to the bare shared type:"group" shape.

createFirestoreMetadataDocument now takes title directly and resolves every scope
field explicitly (context_id, unit/investigation/problem, offering/group) via
getDocumentScopeFields, rather than letting offeringId ride the metadata spread.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ing to group docs [CLUE-550]

Separate the document-kind registry into independent ownerType and scopeType axes
(renamed from ownerScope) and register every built-in kind, so getDocumentScopeFields
resolves scope for all document types from the registry + stores context rather than
from round-tripped RTDB metadata. createFirestoreMetadataDocument now resolves every
scope field (context_id, unit/investigation/problem, offering/group) in one call.

kind/concurrent are stamped into Firestore metadata only for type:"group" docs
(group + class-wide); other kinds are registered for scope/owner resolution but their
kind is deliberately not persisted yet, to avoid migrating publication kinds later.
The on-open concurrent backfill is likewise explicitly gated to GroupDocument.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t the RTDB metadata [CLUE-550]

createFirestoreMetadataDocument no longer receives the round-tripped RTDB metadata
object. It takes a single options object { documentKey, type, kind, owner, createdAt,
title? } and reads context_id/offeringId/groupId from the user store. Every field it
writes to Firestore is now explicit rather than copied from the RTDB metadata via a
cleanedMetadata spread, making the write self-evident and paving the way for removing
the RTDB metadata entirely.

The RTDB round-trip remains only to resolve the server createdAt timestamp, which is
the sole value still sourced from it. uid is passed as owner (equivalent to the prior
type-based branch, since userContext.uid === user.id === the owner for user-scoped kinds).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ment [CLUE-550]

Class-wide documents now resolve their display title live from the kind registry
instead of stamping an authored title into each document's Firestore metadata —
mirroring how problem documents derive their title from the unit config. An author
changing a slot's title applies to every document of that kind, with no stored title
and no migration.

- document-kinds.ts: kinds may register a static `title`; getDocumentTitle(doc) resolves
  a title by kind (class-wide static title, else the group-doc label for other
  type:"group" docs, else undefined so the caller falls back). The group-doc label keys
  on `type` (always present), not `kind` (null on legacy/un-backfilled metadata records);
  the class-wide lookup keys on `kind`, which these new docs always carry.
- document-utils.ts: getDocumentDisplayTitle consults getDocumentTitle first; its
  type===GroupDocument branch is removed.
- db.ts: class-wide slots register their title on the kind; the authored title no longer
  threads through the canonical creation path (createDocument/createFirestoreMetadataDocument
  keep title for personal/learning-log docs, which do persist and display a stored title).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ure [CLUE-550]

Add the Stage-2 design spec describing what the class-wide-slots PR delivers,
including the post-implementation refactors (kind-driven owner/scope derivation,
createFirestoreMetadataDocument options object, collapsed RTDB group metadata,
titles resolved by kind).

Update the document-axes roadmap and target-architecture to match: owner/scope
axes described via ownerType/scopeType, and scope's read side reframed as
field/axis guards with a unified scope getter left as an open question.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scytacki and others added 2 commits July 26, 2026 22:59
…-550]

Rename the classWideDocuments config-entry variables and params (slot/slots ->
classWideDoc/classWideDocs) so .kind/.title read naturally and do not overload
the precise `kind` term; the canonical pointer-slot terminology is preserved.
Also tighten several surrounding comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Convert multi-line JSDoc to block style (delimiters on their own lines),
shorten/reword several comments, and drop a stale label note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e kinds [CLUE-550]

registerDocumentKind is now the single home for kind checking: a kind must be a
camelCase identifier (safe as a Firestore path segment / registry key) and must
be registered exactly once; both violations throw. isValidDocumentKind moved from
document-types.ts to document-kinds.ts alongside it.

createDeclaredClassWideDocuments catches those throws and skips the offending
class-wide entry rather than crashing startup. Built-in kinds register via
registerBuiltInDocumentKinds(); resetDocumentKindRegistryForTests() lets tests
work with the now-throwing registry.

Rename the example class-wide kinds driving-question-board -> drivingQuestionBoard
and word-wall -> wordWall, and the hyphenated test kinds to camelCase.

Document the kind format and the classWideDocuments option in unit-configuration.md,
and add a CLAUDE.md reminder to keep that doc in sync with authorable config changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scytacki and others added 2 commits July 27, 2026 07:11
…UE-550]

The icon field was authored but inert — nothing read it. Remove it from the
config type and the authoring doc; icon presentation (and its config shape) is a
Stage-3 decision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reflect the changes made during review: registry-derived owner/scope
(ownerType/scopeType), the createFirestoreMetadataDocument options object,
collapsed RTDB metadata (DBClassWideDocMetadata removed), titles resolved by
kind, kind validation + uniqueness in registerDocumentKind, the classWideDoc
rename, and dropping the unused icon field (classWideDocuments is { kind, title }).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p owners [CLUE-550]

Firestore rules `hasCanonicalScope` used a bare `!= ""` test, which reads a
field written as null (class-scoped docs store `unit: null`) as present, since
`null != ""` is true in rules. Add a `hasScopeField` helper requiring a
non-empty string and use it for both the scope check and the pointer-path
branch selection, so a null-valued field counts as absent scope.

Group owner ids are `group_<offeringId>_<groupId>`, so require both offeringId
and currentGroupId when creating group-owned documents (was currentGroupId
only), in validateDocumentKindCreation and the getOrCreateGroupDocument guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s/qa unit [CLUE-550]

Lets the class-wide create/pointer flow be exercised end-to-end against a
loadable demo unit: opening qa auto-creates one class-wide document per class
per unit and claims its canonical pointer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…spec [CLUE-550]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@scytacki

Copy link
Copy Markdown
Member Author

The failing Cypress test seems to be random. It failed one time for me before and then passed after a re-run.

Base automatically changed from CLUE-550-concurrent-kind-axes to master July 30, 2026 21:54
…-slots

# Conflicts:
#	docs/document-axes/target-architecture.md
#	firestore.rules
#	src/models/document/document-kinds.ts
#	src/models/document/document-utils.test.ts

@tealefristoe tealefristoe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good 👍

I left a bunch of comments, which I hope are helpful, but take them or leave them as you like. If any show a misunderstanding on my part, please let me know. I'm trying to wrap my head around all of this.

Comment thread docs/superpowers/specs/2026-07-23-clue-550-stage-2-class-wide-slots-design.md Outdated
Comment thread firebase-test/src/canonical-pointers-rules.test.ts Outdated
Comment thread src/lib/db.ts
Comment thread src/lib/db.ts Outdated
Comment thread src/lib/db.ts Outdated
Comment thread src/lib/db.ts Outdated
Comment thread src/lib/db.ts
scytacki and others added 2 commits August 4, 2026 08:35
- Share the group-context check between validateDocumentKindCreation and
  getOrCreateGroupDocument via requireGroupContext, which also narrows
  groupId/offeringId to strings for the pointer path and legacy lookup.
- Move the class-wide kind shape into document-kinds.ts as
  registerClassWideDocumentKind, so every kind shape is declared alongside
  the built-in ones and db.ts passes only the authored kind and title.
- Document the axis-stamping gate in the document-axes roadmap ("Which
  documents get stamped"): how it widens as each type is converted, when it
  can be removed, and why an axis the security rules enforce cannot be
  stamped by the client-side backfill. Link it from both stamp sites.
- Rewrite the design doc's first-session history ordering section as a
  self-contained design decision, and drop the other references to an
  external review list.
- Use camelCase kinds in the registry tests; reflow a comment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-slots

# Conflicts:
#	docs/unit-configuration.md
#	src/models/stores/app-config-model.test.ts
#	src/models/stores/app-config-model.ts
#	src/models/stores/configuration-manager.ts
#	src/models/stores/unit-configuration.ts
@scytacki
scytacki merged commit e077922 into master Aug 4, 2026
31 of 32 checks passed
@scytacki
scytacki deleted the CLUE-550-class-wide-slots branch August 4, 2026 13:09
@scytacki

scytacki commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

@tealefristoe thank you for the thorough review. I do think this is worthwhile especially in this part of the codebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants