Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
125 lines
11 KiB
Markdown
125 lines
11 KiB
Markdown
# Scratchpad — Invoice Template + Designer Safety Hardening
|
|
|
|
- Plan slug: `invoice-template-designer-safety-hardening`
|
|
- Created: `2026-02-13`
|
|
|
|
## What This Is
|
|
|
|
Keep a lightweight, continuously-updated log of discoveries and decisions made while implementing this plan.
|
|
|
|
Prefer short bullets. Append new entries as you learn things, and also *update earlier notes* when a decision changes or an open question is resolved.
|
|
|
|
## Decisions
|
|
|
|
- (2026-02-13) Canonical designer state is `node.props` + `node.children`.
|
|
- Rationale: eliminates dual-source-of-truth bugs and aligns with the unified component AST direction.
|
|
- (2026-02-13) Generic patch/mutation API (`setNodeProp`/`unsetNodeProp`) must reject prototype-pollution keys in paths.
|
|
- Rationale: path-based writes are otherwise a common source of prototype pollution.
|
|
- (2026-02-13) Leaf array `unset` splices the array (removes the element) instead of writing `undefined`.
|
|
- Rationale: avoids sparse arrays and avoids JSON serialization converting `undefined` to `null` in history snapshots.
|
|
- (2026-02-13) Invoice template AST style identifiers are validated (reject invalid) rather than sanitized at render time.
|
|
- Rationale: sanitization can create collisions; validation fails fast and keeps rendering deterministic.
|
|
|
|
## Discoveries / Constraints
|
|
|
|
- (2026-02-13) Recent tracked secrets: `server/.env.local.bak.*` were committed and then removed in `fc2625507`.
|
|
- (2026-02-13) Added repo `.gitignore` patterns to ignore env-backup files (e.g. `**/.env*.bak*`, `server/.env.local.bak*`) to prevent credential leaks.
|
|
- (2026-02-13) Added repo guardrails to fail CI if any tracked file matches `.env*.bak*` patterns:
|
|
- Script: `scripts/guard-no-tracked-env-backups.mjs`
|
|
- CI: `.github/workflows/secrets-env-backup-guard.yml`
|
|
- (2026-02-13) Current patch implementation writes to both `props.*` and legacy top-level fields via `expandPaths`:
|
|
- `packages/billing/src/components/invoice-designer/state/designerStore.ts`
|
|
- (2026-02-13) Designer store now normalizes legacy patch paths (`name`, `metadata.*`, `layout.*`, `style.*`) to canonical `props.*` and writes only canonical state.
|
|
- `packages/billing/src/components/invoice-designer/state/designerStore.ts`
|
|
- (2026-02-13) Legacy node fields (`name`, `metadata`, `layout`, `style`) are treated as derived views of canonical `props.*` (no independent writes).
|
|
- `packages/billing/src/components/invoice-designer/state/designerStore.ts`
|
|
- (2026-02-13) Hierarchy mutations now treat `node.children` as canonical and no longer rely on or update `childIds`.
|
|
- `packages/billing/src/components/invoice-designer/state/patchOps.ts`
|
|
- `packages/billing/src/components/invoice-designer/state/designerStore.ts`
|
|
- (2026-02-13) UI call sites now use canonical `props` reads via `getNodeName/getNodeMetadata/getNodeLayout/getNodeStyle` (no direct legacy field reads in UI components touched).
|
|
- `packages/billing/src/components/invoice-designer/DesignerShell.tsx`
|
|
- `packages/billing/src/components/invoice-designer/palette/OutlineView.tsx`
|
|
- `packages/billing/src/components/invoice-designer/labelText.ts`
|
|
- Removed stale `layout.mode`/`layout.sizing` references from section-fit messaging; CSS-first layout now keys off `layout.display`.
|
|
- (2026-02-13) Continuous typing interactions in the metadata inspector now use `commit=false` on `onChange` and `commit=true` on `onBlur` to avoid history spam.
|
|
- `packages/billing/src/components/invoice-designer/DesignerShell.tsx`
|
|
- (2026-02-13) Patch ops currently allow arbitrary object keys and could write `__proto__` unless guarded:
|
|
- `packages/billing/src/components/invoice-designer/state/patchOps.ts`
|
|
- (2026-02-13) Patch ops now reject prototype-pollution path segments (`__proto__`, `prototype`, `constructor`) at any depth (safe no-op):
|
|
- `packages/billing/src/components/invoice-designer/state/patchOps.ts`
|
|
- Rejection behavior: returns the original `nodes` reference and performs no mutation or history side-effects.
|
|
- Observability: rejected patches emit a dev-only `console.warn`.
|
|
- (2026-02-13) Patch ops `unset` for leaf array indices now splices the array (removes element, no `undefined` holes).
|
|
- `packages/billing/src/components/invoice-designer/state/patchOps.ts`
|
|
- Nested unsets inside array elements keep the element in place; if the element becomes empty it is left as `{}` to remain JSON-serializable.
|
|
- (2026-02-13) Patch ops now reject non-JSON values in `setNodeProp` (e.g. `undefined`, `NaN`, `Infinity`, functions, class instances) to keep history snapshots deterministic.
|
|
- `packages/billing/src/components/invoice-designer/state/patchOps.ts`
|
|
- Array `set` fills missing indices with `null` to avoid sparse arrays (JSON stringification would otherwise introduce `null` implicitly).
|
|
- (2026-02-13) Invoice template AST renderer emits CSS selectors/vars based on unvalidated identifiers:
|
|
- `packages/billing/src/lib/invoice-template-ast/react-renderer.tsx`
|
|
- `packages/billing/src/lib/invoice-template-ast/schema.ts`
|
|
- (2026-02-13) Invoice template AST schema now validates style identifiers (class keys, token ids, styleRef tokenIds) against a strict safe identifier regex and rejects invalid inputs.
|
|
- `packages/billing/src/lib/invoice-template-ast/schema.ts`
|
|
- (2026-02-13) Invoice template AST renderer now sanitizes style identifiers before emitting CSS selectors and custom properties (defense-in-depth even if schema validation is bypassed).
|
|
- `packages/billing/src/lib/invoice-template-ast/react-renderer.tsx`
|
|
- (2026-02-13) Legacy workspace `nodes[]` imports can include `props: {}`; snapshotting now materializes canonical `props.name/metadata/layout/style` from legacy top-level fields so UI helpers can consistently read canonical props.
|
|
- `packages/billing/src/components/invoice-designer/state/designerStore.ts`
|
|
- Test: `packages/billing/src/components/invoice-designer/state/designerStore.loadWorkspace.legacy.test.ts`
|
|
- (2026-02-13) Added hierarchy unit coverage to ensure canonical `children` is authoritative and `childIds` is not used/written during mutations.
|
|
- Test: `packages/billing/src/components/invoice-designer/state/patchOps.insertChild.test.ts`
|
|
- (2026-02-13) Extended hierarchy unit coverage for `moveNode` to assert ordering adjustments, cycle prevention, and canonical `children` authority (no legacy `childIds` writes).
|
|
- Test: `packages/billing/src/components/invoice-designer/state/patchOps.moveNode.test.ts`
|
|
- (2026-02-13) Extended hierarchy unit coverage for `deleteNode` to ensure subtree traversal keys off canonical `children` and does not mutate legacy `childIds`.
|
|
- Test: `packages/billing/src/components/invoice-designer/state/patchOps.deleteNode.test.ts`
|
|
- (2026-02-13) Added UI integration coverage to ensure Outline/tree rendering prefers canonical `props.name` over legacy top-level `name`.
|
|
- Test: `packages/billing/src/components/invoice-designer/palette/OutlineView.integration.test.tsx`
|
|
- (2026-02-13) Added a focused DesignerShell UI test to ensure the selected-node header uses `getNodeName` (canonical `props.name` takes precedence over legacy `name`).
|
|
- Test: `packages/billing/src/components/invoice-designer/DesignerShell.selectedContext.integration.test.tsx`
|
|
- (2026-02-13) Added unit coverage for leaf array `unset` semantics: leaf index unsets splice the array (no `undefined` holes).
|
|
- Test: `packages/billing/src/components/invoice-designer/state/patchOps.unsetNodeProp.test.ts`
|
|
- (2026-02-13) Added unit coverage to ensure nested unsets inside array elements remove only the nested property (do not splice the element).
|
|
- Test: `packages/billing/src/components/invoice-designer/state/patchOps.unsetNodeProp.test.ts`
|
|
- (2026-02-13) Added undo/redo coverage for canonical JSON snapshots involving array edits (set + leaf-array unset splice) to ensure history restores exact committed state.
|
|
- Test: `packages/billing/src/components/invoice-designer/state/designerStore.undoRedo.test.ts`
|
|
- (2026-02-13) Added undo/redo coverage for a mixed sequence of `setNodeProp` + hierarchy ops (move/delete) to ensure history replay is deterministic.
|
|
- Test: `packages/billing/src/components/invoice-designer/state/designerStore.undoRedo.test.ts`
|
|
- (2026-02-13) Added direct store-level tests for `commit=false` vs `commit=true` semantics to prevent history spam during continuous interactions.
|
|
- Test: `packages/billing/src/components/invoice-designer/state/designerStore.historyCommit.test.ts`
|
|
- (2026-02-13) Added DesignCanvas pointer-driven resize integration test asserting `onResize(..., commit=false)` during motion and a final `commit=true` on pointer-up.
|
|
- Test: `packages/billing/src/components/invoice-designer/canvas/DesignCanvas.resizeProps.integration.test.tsx`
|
|
- (2026-02-13) Added schema coverage ensuring invoice-template AST style class keys must pass safe CSS identifier validation.
|
|
- Test: `packages/billing/src/lib/invoice-template-ast/schema.test.ts`
|
|
- (2026-02-13) Added schema coverage ensuring token ids and styleRef tokenIds must pass safe CSS identifier validation.
|
|
- Test: `packages/billing/src/lib/invoice-template-ast/schema.test.ts`
|
|
- (2026-02-13) Added authoritative preview coverage: schema validation failures return compile diagnostics and do not proceed to render HTML/CSS output.
|
|
- Test: `packages/billing/src/actions/invoiceTemplatePreview.schemaFailure.test.ts`
|
|
- (2026-02-13) Added renderer defense-in-depth coverage: even if invalid identifiers bypass schema validation, CSS selectors/vars and HTML class names are sanitized.
|
|
- Test: `packages/billing/src/lib/invoice-template-ast/react-renderer.test.tsx`
|
|
|
|
## Commands / Runbooks
|
|
|
|
- (2026-02-13) Check for tracked env backups:
|
|
- `git ls-files | rg "\\.env\\.local\\.bak\\.|\\.env\\.bak\\."`
|
|
- (2026-02-13) Check `.gitignore` contains env-backup ignore patterns:
|
|
- `node scripts/test-gitignore-env-backups.mjs`
|
|
- (2026-02-13) Test env-backup guard script (pass + fail cases in a temp git repo):
|
|
- `node scripts/test-guard-no-tracked-env-backups.mjs`
|
|
- (2026-02-13) Run invoice-designer unit tests (Vitest config root is `server/`):
|
|
- `cd server && npx vitest run ../packages/billing/src/components/invoice-designer/state/patchOps.setNodeProp.test.ts`
|
|
- (2026-02-13) Gotcha: Vitest can fail with `ENOSPC` (no space left on device); running with `--coverage=false` reduces temp output and avoided the issue locally.
|
|
|
|
## Links / References
|
|
|
|
- PRDs, issues, PRs, docs, dashboards, logs, and key file paths.
|
|
- Key files:
|
|
- `packages/billing/src/components/invoice-designer/state/patchOps.ts`
|
|
- `packages/billing/src/components/invoice-designer/state/designerStore.ts`
|
|
- `packages/billing/src/components/invoice-designer/utils/nodeProps.ts`
|
|
- `packages/billing/src/lib/invoice-template-ast/schema.ts`
|
|
- `packages/billing/src/lib/invoice-template-ast/react-renderer.tsx`
|
|
|
|
## Open Questions
|
|
|
|
- Questions that block the work or need follow-up.
|
|
- Do we want patch rejection to be silent no-op, console-warn, or surfaced as a formal diagnostic in the designer UI?
|