Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
node-version: '22'
cache: npm
- run: npm ci
- run: npm run typecheck
- run: npm test
- run: npm audit --audit-level=moderate --omit=dev
- run: npm run build
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@
- Server: `npm audit fix` clears a critical advisory in `proxy-addr` (a production dependency of Express); the sync server's Docker image moves from Node 20 (end-of-life) to Node 22.

### Fixed
- The demo's tenants and vendors were missing the `documentUrl` field every real record has (found by the new type check).
- Per-task **notes and custom fields** are now part of the portable workspace (schema v3): they were missing from JSON export, Copy State, and Team Sync, and were not restored from backups. Version 2 files still import.
- **Reset now deletes automatic backups** (full workspace copies, tenant records included) with "Operating workspace" and "Everything"; its confirmation says what a reset does not touch. Team Sync is stopped first so it cannot re-save the passphrase.
- Risk Queue grammar: "Compliance **needs** clearer ownership" when exactly one lane is weak (was "need"). The demo's team card now says "fictional people" instead of "real people".
- Task rows no longer squeeze the task name under the status pill when a task has a due date, a dependency, and notes.
- Work orders without a timestamp no longer display the text "Invalid Date".

### Added
- **Permanent task ids.** Every starter task in `config.json` now has an id such as `maintenance-014`, assigned once by `npm run ids:assign` (it only adds missing ids, never changes one). Saved progress, imports, backups, undo and task dependencies match by id first, then by starter name, then by a task's `aliases` (former names), so rewording a task in `config.json` no longer orphans anyone's progress. A task whose name you never edited follows the new wording; one you renamed keeps your name. Export schema is now **v4** (adds `id` per task and `taskId` on dependencies); v2 and v3 files still import.
- **Field-level import review.** A small hand-rolled schema layer (`js/schema.js`, no dependencies) now powers the sanitizers. The import review lists exactly what will be repaired or skipped, by record and field — for example *Work order 3 ("Fix sink") — priority: "urgent!!" is not allowed; reset to "medium"* — including task rows that match nothing in this version and unknown departments. Repairs made while loading saved data are recorded and logged to the console instead of happening silently.
- **Type checking.** `npm run typecheck` (TypeScript over JSDoc, `strict`, no build step, no migration to `.ts`) now runs in CI. Shared shapes live in `js/types.js`. The state, storage, import/export schema, sanitizers, task identity, sync envelope, demo and utility modules are fully checked; the views and a few large modules are marked `// @ts-nocheck` until converted, and a test stops the checked set from shrinking.
- **Sync hardening (client and server).** The client checks the server's reply envelope (`state`, `version`) and shows a clear message instead of a TypeError when the reply is malformed. The sync server now rejects pushes that are not shaped like a workspace (wrong section types, oversized lists, `__proto__` keys, extreme nesting, a non-integer `expectedVersion`, a passphrase over 200 characters).
- **Live demo** at `?demo=1`: a fictional company with gaps, an overloaded teammate, blocked work, and work orders in every stage, with a permanent "Fictional data only" banner, one-click reset, isolated storage (`pm-ops-demo-*` keys), and Team Sync disabled. Deployed to GitHub Pages by `.github/workflows/pages.yml` after the production build passes its own browser tests.
- **In-product privacy messaging**: a Portfolio notice, a *Data & Privacy* dialog, a first-run note, a once-per-session confirmation before exporting resident data, and honest *Beta · trusted-team sync* labelling for Team Sync.
- Playwright browser suite (security fuzzing, CSV, data safety, privacy, demo, layout, smoke, and built-site tests) running in CI; Dependabot and a weekly production-dependency audit.
Expand Down
27 changes: 25 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,34 @@ pm-ops-map/
│ ├── sync.js Optional team sync client — talks to server/
│ ├── launchPlan.js Beginner setup dashboard and readiness checks
│ ├── handbook.js Markdown handbook export
│ ├── normalize.js Sanitizers for every path that loads outside data
│ ├── schema.js Hand-rolled schema layer: field helpers, record/collection validation, error paths
│ ├── normalize.js Sanitizers for every path that loads outside data (built on schema.js)
│ ├── syncEnvelope.js Checks the shape of Team Sync server replies
│ ├── types.js Shared JSDoc type definitions (no runtime code)
│ ├── taskIdentity.js Permanent task ids: stamping at boot and matching saved rows to tasks
│ ├── privacy.js Data-handling guidance and export confirmations
│ ├── demoMode.js ?demo=1 detection and storage-key namespacing
│ ├── demo.js Hosted demo: seeding, banner, reset
│ ├── showcase.js The fictional workspace the demo loads
│ ├── views/ Tracking, map, team, portfolio, and work order screens
│ └── __tests__/ Vitest unit tests — they import the shipped ES modules directly
├── scripts/ Maintenance scripts (assign-task-ids.mjs)
├── e2e/ Playwright browser tests (security regression suite)
└── server/ Optional sync server — separate package.json, own deps, own tests
```

Starter operating data lives in `config.json`. Runtime state is loaded by `app.js`, kept in `state.js`, and persisted by `storage.js`. Feature rendering lives in the relevant `js/views/` module.

## Editing `config.json` tasks

Every task has a permanent `id` (`<department>-<NNN>`, e.g. `maintenance-014`). Saved data, imports and backups find a task by that id, so its visible `name` is free to change.

- **Adding a task:** write `{ "name": "…", "owner": "UNOWNED" }` on its own line, then run `npm run ids:assign`. It adds ids to tasks that lack one and never changes an existing id. A test fails if any task has no id or two share one.
- **Rewording a task:** change `name` only — never the `id`. Also list the old wording in `"aliases": ["old name"]` so data saved *before ids existed* (and old exports) still finds the task. Data saved since ids shipped follows the id on its own.
- **Moving a task to another department:** keep its `id`; progress follows it.
- **Removing a task:** delete the line. Do not reuse its id for a different task.
- Keep each task on a single line; the id script refuses a layout it can't edit safely.

## Security Conventions

PM Ops Map renders data it did not create — imported JSON, pasted clipboard text, Team Sync snapshots, restored backups — so every contribution follows these rules:
Expand All @@ -103,6 +118,14 @@ PM Ops Map renders data it did not create — imported JSON, pasted clipboard te

`npm run test:e2e` runs a browser suite (`e2e/security-xss.spec.js`) that injects a canary payload into every importable field and fails if any view turns it into markup or script. If you add a field or a view, extend `e2e/helpers/security.js`.

## Types (JSDoc + `npm run typecheck`)

The client is plain JavaScript with no build step, and it is type-checked with JSDoc comments: `npm run typecheck` runs `tsc` over `js/` (`allowJs`, `checkJs`, `strict`, `noEmit`). CI runs it before the tests.

- Shared shapes (Task, Employee, WorkOrder, Property, Tenant, Vendor, WorkspacePayload, ImportReport, …) live in `js/types.js`. Pull them into a file with `/** @import { Task } from './types.js' */`.
- Annotate with `@param` / `@returns` / `@type`. Don't use `any`: if a value really is untrusted, type it `unknown` and narrow it (see `normalize.js`); if you must reach for `any`, say why in a comment next to it.
- Files that are not converted yet start with `// @ts-nocheck — TODO(types): …`. To convert one, delete that line, fix what `npm run typecheck` reports, and remove it from `NOT_CONVERTED_YET` in `js/__tests__/typecheck-guard.test.js`. New files are checked from the start.

## Dependency Updates

Dependabot opens weekly update PRs (`.github/dependabot.yml`): minor and patch bumps are grouped, every major bump is its own PR, and the sync server's production dependencies (Express, cors) are grouped apart from test tooling. CI audits production dependencies of both packages on every push, and `.github/workflows/audit.yml` repeats that weekly so a newly published advisory fails loudly even when nobody is pushing. When a Dependabot PR is green, merge it; when it is a major bump, read the changelog first.
Expand All @@ -116,7 +139,7 @@ The client (`index.html`, `js/`, `css/`) has no runtime npm dependencies and sho
- If you're editing an HTML-called handler, update both `index.html` and the `Object.assign(window, ...)` block in `js/app.js`
- If you're editing a view, keep changes in the relevant `js/views/` module when possible
- No new runtime dependencies in the client app (`index.html`, `js/`, `css/`) — it should keep working as browser-native HTML, CSS, and JavaScript with zero installs. `server/` is a separate package and may have its own minimal dependencies.
- Run `npm test` before submitting — tests must pass. If you touched rendering, import/export, or storage code, also run `npm run test:e2e` (one-time setup: `npx playwright install chromium`). If you touched `server/`, also run `npm test` inside `server/`.
- Run `npm run typecheck` and `npm test` before submitting — both must pass. If you touched rendering, import/export, or storage code, also run `npm run test:e2e` (one-time setup: `npx playwright install chromium`). If you touched `server/`, also run `npm test` inside `server/`.
- Test the real modules: import from `js/*.js` in your tests. Don't add `.cjs` mirrors or test-only copies of browser code.

## Code Style
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ Edit `config.json` in the project root. No JavaScript needed.
}
```

Add a new department the same way, using a new `id`.
Add a new department the same way, using a new `id`. After adding tasks, run `npm run ids:assign` — every task carries a permanent id (like `leasing-006`) that keeps saved progress attached even if you reword the task later. See [CONTRIBUTING](CONTRIBUTING.md#editing-configjson-tasks).

### Fork and brand it

Expand Down Expand Up @@ -419,6 +419,7 @@ pm-ops-map/
│ ├── handbook.js Markdown + HTML operations handbook generator
│ ├── stateSchema.js Versioned JSON export/import schema and validation
│ ├── normalize.js Boundary sanitizers for imports, sync, backups, and storage loads
│ ├── taskIdentity.js Permanent task ids: boot stamping and saved-row → task matching
│ ├── privacy.js Data-handling guidance, Data & Privacy dialog, export confirmations
│ ├── demoMode.js ?demo=1 detection and storage-key namespacing (no dependencies)
│ ├── demo.js Hosted demo: seeding, banner, reset, sync disabled
Expand All @@ -437,6 +438,7 @@ pm-ops-map/
│ ├── utils.test.js Utility function tests
│ ├── stateSchema.test.js
│ └── templates.test.js
├── scripts/ assign-task-ids.mjs — adds permanent ids to new config.json tasks
├── e2e/ Playwright browser tests, including the XSS regression suite
├── server/ Optional self-hosted sync server (own package.json, tests, Docker) — see server/README.md
├── docker-compose.yml Runs the sync server locally with durable storage
Expand Down Expand Up @@ -470,7 +472,7 @@ pm-ops-map/

### Data flow

`config.json` is fetched on load. `app.js` stamps each task with a stable `_configName` identity key and passes the result to `state.js`. Every module imports from `state.js`. UI edits mutate in-memory objects; `storage.js` persists them to `localStorage` after each change. Nothing goes to a server unless `sync.js` is connected to a Team Sync workspace.
`config.json` is fetched on load. `app.js` stamps each task with its starter name (`_configName`), makes sure it has a permanent `id`, and passes the result to `state.js`; saved data is matched back by `id` first (see `js/taskIdentity.js`). Every module imports from `state.js`. UI edits mutate in-memory objects; `storage.js` persists them to `localStorage` after each change. Nothing goes to a server unless `sync.js` is connected to a Team Sync workspace.

### Module graph (no circular dependencies)

Expand Down
Loading
Loading