Hermes 284313f908
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
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

11 KiB

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.
  • 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?