Repository navigation
Phase 1A: stable task IDs, runtime schemas, and JSDoc type checking - #64
Merged
Merged
Conversation
Every task in config.json now carries an id of the form <dept>-<NNN> (e.g. maintenance-014), assigned once by scripts/assign-task-ids.mjs. The script only ever ADDS missing ids: existing ids are never changed, numbering continues after the highest number used in each department, it edits config.json as text so the diff is one inserted "id" per task line, and it re-parses its own output and refuses to write if anything other than ids would change. `npm run ids:assign` / `npm run ids:check`. Tests: all 262 present, well-formed, unique, not derived from names; script is idempotent, never reuses numbers, rejects duplicate ids and layouts it cannot edit safely. Mutation-checked (reassigning existing ids fails 3 tests). Runtime matching by id comes in the next commits; behavior is unchanged here. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXAgt2SAabUwyuTA9TbMsr
…y permanent task id Step 1 of Phase 1. Renaming a starter task in config.json no longer orphans anyone's progress. - js/taskIdentity.js (pure): stampTaskIdentity() records _configName and guarantees a unique, safe id at boot (deterministic fallback for custom configs without ids); findConfigTask() matches saved rows by id first, then by _configName or the task's `aliases` (former names) inside the saved department; findBlockerTask() does the same for dependencies. - applySavedTasks (the one merge point: storage load, import, backup restore, undo, sync) and validateImportedState now share that matcher, so the import review can no longer promise a match the import won't make (it previously also matched by display name). - A name the user never edited follows config.json's current wording; an edited name is kept. Restoring an unedited row puts the starter name back. - Dependencies: blockedBy gains taskId (configName still written and read); moved/renamed blockers keep blocking. - Export schema v4 adds id per task. v2 and v3 files (no ids) still import. - localStorage, undo snapshots, showcase and backups carry the id. - e2e canary payloads include hostile id/taskId values. Verified: failing tests first; mutation-checked the matcher (id, alias and department scoping), storage writer, export payload and dependency writer. A real-browser test rewrites config.json mid-test and reloads. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXAgt2SAabUwyuTA9TbMsr
… team card Spotted on the live demo: - Risk Queue said "Compliance need clearer ownership" for a single weak lane. coverageRiskDetail() now picks needs/need by count (still names at most 3). - The team card called the demo's fictional staff "real people"; in demo mode it now says "fictional people". Tests first: singular/plural/cap, and real-workspace vs demo label. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXAgt2SAabUwyuTA9TbMsr
…ield and record it repairs
Step 2 of Phase 1 (part 1). js/schema.js is a small, dependency-free schema layer
(file://-friendly): field helpers, validateRecord and validateCollection, and
error objects { path, field, code, message } such as
Work order 3 ("Fix sink") — priority: "urgent!!" is not allowed; reset to "medium".
at path workOrders[2].priority.
- normalize.js keeps its public API and now builds on the layer; team members,
work orders, properties, tenants, vendors and saved task rows all report
what they repaired, dropped (missing required field, duplicate, reserved
name, over the limit) or unlinked (unknown property / department).
Result objects gain `errors`; kept/dropped/repaired stats are unchanged.
- validateImportedState adds report.issues (+ issueCount, capped at 100) and
also reports task rows with no matching task and departments this version
doesn't have. formatImportReport prints them.
- The import review modal shows a "What will be repaired" list (escaped; hostile
values are shortened to one quoted line).
All 346 pre-existing tests passed unchanged through the refactor. Failing tests
first; mutation-checked (field errors, sanitizer errors in the report, modal
rendering, unmatched-row reporting).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXAgt2SAabUwyuTA9TbMsr
…, and pushes on the server Step 2 of Phase 1 (part 2). Client - js/syncEnvelope.js: pull / version / push / 409-conflict replies are checked (object, state is an object, version is a safe non-negative integer) before use; a malformed reply now gives "The sync server sent an unexpected response: …" instead of a TypeError inside the sync loop (a 200 with a non-JSON body parsed to null and crashed on remote.state). - storage.js: every loader records what it had to repair (source, field path, sentence) via getLoadIssues() and warns once in the console. No UI change. - applySavedTasks labels rows by path when asked for issues. Server (CommonJS, minimal) - server/src/validate.js: state must be an object with the right section types, bounded list sizes, no __proto__ keys, depth <= 20, optional schema name/version checks; expectedVersion must be a whole number; passphrase <= 200 chars. 400 with a message naming the problem; nothing is stored. Unknown keys stay allowed. Minimum passphrase length etc. are untouched (Phase 5). Tests first for each; mutation-checked (envelope parser, storage recorder, server state validator and expectedVersion check). Server suite 19 -> 33. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXAgt2SAabUwyuTA9TbMsr
…nert Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXAgt2SAabUwyuTA9TbMsr
…ata/boundary modules) Step 3 of Phase 1. TypeScript is a devDependency only; the client stays plain JavaScript with no build step and no migration to .ts. - tsconfig.json: allowJs, checkJs, strict, noEmit over js/ (tests excluded). - js/types.js: shared typedefs - Task, Department, Employee, Team, WorkOrder, Property, Tenant, Vendor, Portfolio, AuditEntry, BlockedBy, WorkspacePayload, Issue, ImportReport (no runtime code). - Strict and clean: state, storage, stateSchema, normalize, schema, taskIdentity, syncEnvelope, privacy, showcase, demo, demoMode, templates, utils, types. Untrusted input is `unknown` and narrowed; the only explicit `any` (the imported-file view in sanitizeWorkspace) says why. - Everything else starts with `// @ts-nocheck - TODO(types)`; a test pins the list so the converted set cannot shrink and new files are checked by default. - CI runs `npm run typecheck` before the tests. Found by the checker: the demo's tenants/vendors lacked `documentUrl` (every sanitized record has it). A failing test (showcase equals its sanitized self) came first. Only runtime edits besides that: utils getLeaseStatus uses end.getTime() - today.getTime(), formatWODate uses `isoStr ?? NaN`, getOpsProfile parses `?? 'null'` - each identical in behavior. Mutation-checked: wrong argument types and a typo'd property are both rejected by tsc. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXAgt2SAabUwyuTA9TbMsr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First of three PRs for Phase 1 (foundations). It covers steps 1-3 and keeps behavior identical except where it fixes a bug. Groups B (pure domain layer, one
commit()path) and C (event delegation, E2E expansion) will follow once this is merged.1. Stable task IDs
config.jsonnow have a permanent id likeleasing-001, assigned once bynpm run ids:assign. The script only adds missing ids, never changes one, and checks its own output before writing._configName, then by the task'saliases(former names). The shared matcher lives injs/taskIdentity.js.config.jsonno longer orphans saved progress (covered by a real-browser test that rewritesconfig.jsonand reloads).blockedBygainstaskId(and still writesconfigName). Export schema is now v4. v2 and v3 files still import.2. Runtime schemas
js/schema.js(works fromfile://) returns{ value, errors[] }with field paths.normalize.jsis built on it, and its public API is unchanged.js/syncEnvelope.jschecks the server's reply (state,version) so a malformed reply gives a clear message instead of a TypeError.getLoadIssues()) and logged to the console once. There is no UI change.server/src/validate.js, CommonJS): pushes are rejected with a 400 naming the problem when the state is not an object, a section has the wrong type, a list is oversized, there is a__proto__key, nesting is deeper than 20 levels,expectedVersionis not an integer, or the passphrase is over 200 characters. Unknown keys are still accepted.3. Types without a migration
typescriptis a devDependency.tsconfig.jsonusesallowJs,checkJs,strictandnoEmit.npm run typechecknow runs in CI before the tests.js/types.js. state, storage, stateSchema, normalize, schema, taskIdentity, syncEnvelope, privacy, showcase, demo, demoMode, templates and utils are fully checked, with one commentedany.// @ts-nocheck — TODO(types). A guard test keeps the checked set from shrinking.Also in this PR
documentUrl. It is fixed, with a test.Review guide
The commits are meant to be read in order:
feat(config)is the 262 ids, with a mechanical config diff of one inserted"id"per line.feat(identity)is the matcher and where it is wired in.fix(copy)is the small demo fixes.feat(schema)(2 commits plus an e2e commit) is the schema layer, import review, sync and server.feat(types)is mostly JSDoc.Most of the line count is tests and the
config.jsonids. The runtime changes outside annotations in the types commit are three behavior-identical tweaks, listed in its message.Verification
Every fix was written test-first. I also broke each one on purpose (reintroduced the bug) to confirm the tests fail.
npm run typecheck(new)/pm-ops-map/andfile://)npm audit --omit=dev(root and server)npm run buildKnown and deliberately left alone
assigneebecomes'', not'UNASSIGNED'. The sanitizer's fallback is dead code, and it is outside this step.scryptSync, Docker user, CORS default).Things to look at closely
js/taskIdentity.jsandapplySavedTasksinjs/normalize.js, which is the single place saved data meets starter tasks.🤖 Generated with Claude Code
https://claude.ai/code/session_01GXAgt2SAabUwyuTA9TbMsr
Generated by Claude Code