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
11 KiB
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
unsetsplices the array (removes the element) instead of writingundefined.- Rationale: avoids sparse arrays and avoids JSON serialization converting
undefinedtonullin history snapshots.
- Rationale: avoids sparse arrays and avoids JSON serialization converting
- (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 infc2625507. - (2026-02-13) Added repo
.gitignorepatterns 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
- Script:
- (2026-02-13) Current patch implementation writes to both
props.*and legacy top-level fields viaexpandPaths:packages/billing/src/components/invoice-designer/state/designerStore.ts
- (2026-02-13) Designer store now normalizes legacy patch paths (
name,metadata.*,layout.*,style.*) to canonicalprops.*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 canonicalprops.*(no independent writes).packages/billing/src/components/invoice-designer/state/designerStore.ts
- (2026-02-13) Hierarchy mutations now treat
node.childrenas canonical and no longer rely on or updatechildIds.packages/billing/src/components/invoice-designer/state/patchOps.tspackages/billing/src/components/invoice-designer/state/designerStore.ts
- (2026-02-13) UI call sites now use canonical
propsreads viagetNodeName/getNodeMetadata/getNodeLayout/getNodeStyle(no direct legacy field reads in UI components touched).packages/billing/src/components/invoice-designer/DesignerShell.tsxpackages/billing/src/components/invoice-designer/palette/OutlineView.tsxpackages/billing/src/components/invoice-designer/labelText.ts- Removed stale
layout.mode/layout.sizingreferences from section-fit messaging; CSS-first layout now keys offlayout.display.
- (2026-02-13) Continuous typing interactions in the metadata inspector now use
commit=falseononChangeandcommit=trueononBlurto 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
nodesreference and performs no mutation or history side-effects. - Observability: rejected patches emit a dev-only
console.warn.
- (2026-02-13) Patch ops
unsetfor leaf array indices now splices the array (removes element, noundefinedholes).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
setfills missing indices withnullto avoid sparse arrays (JSON stringification would otherwise introducenullimplicitly).
- (2026-02-13) Invoice template AST renderer emits CSS selectors/vars based on unvalidated identifiers:
packages/billing/src/lib/invoice-template-ast/react-renderer.tsxpackages/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 includeprops: {}; snapshotting now materializes canonicalprops.name/metadata/layout/stylefrom 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
childrenis authoritative andchildIdsis not used/written during mutations.- Test:
packages/billing/src/components/invoice-designer/state/patchOps.insertChild.test.ts
- Test:
- (2026-02-13) Extended hierarchy unit coverage for
moveNodeto assert ordering adjustments, cycle prevention, and canonicalchildrenauthority (no legacychildIdswrites).- Test:
packages/billing/src/components/invoice-designer/state/patchOps.moveNode.test.ts
- Test:
- (2026-02-13) Extended hierarchy unit coverage for
deleteNodeto ensure subtree traversal keys off canonicalchildrenand does not mutate legacychildIds.- Test:
packages/billing/src/components/invoice-designer/state/patchOps.deleteNode.test.ts
- Test:
- (2026-02-13) Added UI integration coverage to ensure Outline/tree rendering prefers canonical
props.nameover legacy top-levelname.- Test:
packages/billing/src/components/invoice-designer/palette/OutlineView.integration.test.tsx
- Test:
- (2026-02-13) Added a focused DesignerShell UI test to ensure the selected-node header uses
getNodeName(canonicalprops.nametakes precedence over legacyname).- Test:
packages/billing/src/components/invoice-designer/DesignerShell.selectedContext.integration.test.tsx
- Test:
- (2026-02-13) Added unit coverage for leaf array
unsetsemantics: leaf index unsets splice the array (noundefinedholes).- Test:
packages/billing/src/components/invoice-designer/state/patchOps.unsetNodeProp.test.ts
- Test:
- (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
- Test:
- (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
- Test:
- (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
- Test:
- (2026-02-13) Added direct store-level tests for
commit=falsevscommit=truesemantics to prevent history spam during continuous interactions.- Test:
packages/billing/src/components/invoice-designer/state/designerStore.historyCommit.test.ts
- Test:
- (2026-02-13) Added DesignCanvas pointer-driven resize integration test asserting
onResize(..., commit=false)during motion and a finalcommit=trueon pointer-up.- Test:
packages/billing/src/components/invoice-designer/canvas/DesignCanvas.resizeProps.integration.test.tsx
- Test:
- (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
- Test:
- (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
- Test:
- (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
- Test:
- (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
- Test:
Commands / Runbooks
- (2026-02-13) Check for tracked env backups:
git ls-files | rg "\\.env\\.local\\.bak\\.|\\.env\\.bak\\."
- (2026-02-13) Check
.gitignorecontains 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=falsereduces 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.tspackages/billing/src/components/invoice-designer/state/designerStore.tspackages/billing/src/components/invoice-designer/utils/nodeProps.tspackages/billing/src/lib/invoice-template-ast/schema.tspackages/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?