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

23 KiB
Raw Blame History

SCRATCHPAD — Invoice Designer Unified Component AST

Context

Goal: collapse designer state into a unified generic node tree so adding new properties (for example borderRadius) is mostly schema work, not store plumbing.

This plan intentionally continues the simplification arc:

  1. Templates are data (templateAst JSON) not code/WASM.
  2. Layout math is delegated to the DOM/CSS engine (flex/grid + dnd-kit).
  3. Designer state becomes a single generic AST with schema-driven editing.

Current State (Starting Point)

  • Designer store uses DesignerNode[] with typed fields:
    • position, size, parentId, childIds, allowedChildren, layout, style, metadata, etc.
  • Property editing is largely hardcoded in packages/billing/src/components/invoice-designer/DesignerShell.tsx.
  • Nesting rules are defined in packages/billing/src/components/invoice-designer/state/hierarchy.ts.
  • Palette metadata defaults live in packages/billing/src/components/invoice-designer/constants/componentCatalog.ts.

Decisions (Implementation Choices That Minimize Trouble)

  • Source of truth for hierarchy is children arrays only.
    • Do not persist parentId (redundant and easy to desync).
    • Parent lookup is derived when needed (tree sizes are small enough for O(n) searches).
  • Patch API uses dot-notation paths (for example style.width, metadata.bindingKey) rather than JSON Pointer to keep call sites readable.
    • Internally, patch operations should be applied immutably (structural sharing).
    • This patch API is a hard requirement for the strategy: without it, the refactor cost is paid without getting the “new prop = schema change” velocity gain.
  • Component schema becomes the single source of truth for:
    • palette labels/descriptions/categories
    • defaults (initial props)
    • editable props schema (inspector UI)
    • allowed parents/children (nesting rules)
  • Inspector will support a small set of core field widgets plus opt-in custom widgets for complex objects/arrays (tables).

Notes / Gotchas

  • Ensure history/undo remains stable:
    • Either snapshot nodesById + rootId per commit, or store patches and replay.
    • Snapshotting is simpler and aligns with current approach; patch replay is smaller but more error-prone.
  • Some existing code expects allowedChildren to be present on nodes; that must become a derived selector from schema.
  • Export/import to invoice-template AST needs a single authoritative mapping; avoid letting schema defaults leak into exported templates unexpectedly.

File Targets (Likely Touchpoints)

  • Store:
    • packages/billing/src/components/invoice-designer/state/designerStore.ts
  • Hierarchy rules removal:
    • packages/billing/src/components/invoice-designer/state/hierarchy.ts (delete)
  • Component schema/catalog:
    • packages/billing/src/components/invoice-designer/constants/componentCatalog.ts (likely merged into or replaced by schema module)
  • Inspector refactor:
    • packages/billing/src/components/invoice-designer/DesignerShell.tsx
  • Canvas rendering:
    • packages/billing/src/components/invoice-designer/canvas/DesignCanvas.tsx
  • Workspace AST mapping:
    • packages/billing/src/components/invoice-designer/ast/workspaceAst.ts

Validation Runbook (When Implementing)

  • Tree invariants:
    • every child id exists in nodesById
    • no cycles (walk from root and track visited)
    • all nodes are reachable from root (or explicitly allow unattached nodes, but prefer not to)
  • Quick grep for legacy references:
    • rg -n "parentId|allowedChildren|updateNodeStyle\\(|updateNodeLayout\\(|updateNodeMetadata\\(" packages/billing/src/components/invoice-designer -S

Developer Guide (Adding Props / Components)

Component Schema Source Of Truth

  • File: packages/billing/src/components/invoice-designer/schema/componentSchema.ts
  • Each component defines:
    • label/description/category (palette + inspector display metadata)
    • defaults: initial name, layout, style, metadata, size
    • hierarchy: allowedParents + allowedChildren
    • inspector: panels/fields (schema-driven Inspector)
  • Helpers now live on the schema module:
    • getAllowedChildrenForType(type)
    • getAllowedParentsForType(type)
    • canNestWithinParent(childType, parentType)

Inspector Schema Format

  • Files:
    • packages/billing/src/components/invoice-designer/schema/inspectorSchema.ts (field/panel types)
    • packages/billing/src/components/invoice-designer/inspector/DesignerSchemaInspector.tsx (renderer)
  • Field kinds supported:
    • primitives: string, number, boolean, enum
    • CSS-ish: css-length, css-color
    • complex: widget (custom React editor for component-specific metadata)
  • Visibility rules:
    • visibleWhen supports nodeIsContainer, pathEquals, parentPathEquals
  • Widgets:
    • Implement widgets in packages/billing/src/components/invoice-designer/inspector/widgets/
    • Register by referencing the widget id in schema (example: table-editor for metadata.columns)

Patch API (How To Mutate State)

  • File: packages/billing/src/components/invoice-designer/state/designerStore.ts
  • Only supported mutation surface (for anything persistent/undoable):
    • setNodeProp(nodeId, path, value, commit?)
    • unsetNodeProp(nodeId, path, commit?)
    • insertChild(parentId, childId, index)
    • removeChild(parentId, childId)
    • moveNode(nodeId, nextParentId, nextIndex)
    • deleteNode(nodeId)
  • Dot-path conventions (recommended):
    • name
    • style.* (sizing/typography/media CSS-like props)
    • layout.* (container layout props)
    • metadata.* (component-specific config)
  • History/undo semantics:
    • commit defaults to true
    • For multi-step edits that should be a single undo step: use commit=false for intermediate writes and commit=true for the final write.

Persistence Rules (What Gets Serialized)

  • Persisted workspace snapshot is { rootId, nodesById, canvas settings }.
  • exportWorkspace() sanitizes node props to drop runtime/editor-only keys:
    • position, size, baseSize, layoutPresetId
  • If a prop should survive reload and affect output, it should live under:
    • props.style, props.layout, or props.metadata (and be surfaced via schema).

Conventions For Adding A New Property

  1. Add/extend the schema field in componentSchema.ts (usually via COMMON_INSPECTOR or component-specific inspector).
  2. Provide a default in defaults.style / defaults.layout / defaults.metadata only if it must exist on insertion.
  3. Avoid adding store actions/reducers: use setNodeProp/unsetNodeProp from inspector and other UI.

Conventions For Adding A New Component

  1. Add a DesignerComponentSchema entry in DESIGNER_COMPONENT_SCHEMAS.
  2. Define hierarchy.allowedParents and hierarchy.allowedChildren (the only authority for nesting rules).
  3. Define defaults and inspector panels/fields.
  4. Canvas/palette/outline should not need bespoke wiring if schema is complete.

Progress Log

  • 2026-02-13: Implemented unified AST type definitions in packages/billing/src/components/invoice-designer/state/designerAst.ts:
    • DesignerAstNode with { id, type, props, children }
    • DesignerAstWorkspace with { rootId, nodesById }
    • Stable document root id constant: DESIGNER_AST_DOCUMENT_ID = 'designer-document-root'
  • 2026-02-13: Added canonical indexing to the designer store state in packages/billing/src/components/invoice-designer/state/designerStore.ts:
    • Store state now includes rootId and nodesById kept in sync with the existing nodes array via a setWithIndex wrapper.
    • This is an incremental cutover step so downstream UI/tests can migrate off nodes progressively.
  • 2026-02-13: Implemented generic patch operations in packages/billing/src/components/invoice-designer/state/patchOps.ts and exposed them on the store:
    • setNodeProp / unsetNodeProp for dot-path updates (immutable deep updates with empty-object cleanup).
    • insertChild / removeChild / moveNode / deleteNode for hierarchy mutations (cycle prevention in moveNode).
    • Renamed legacy coordinate nudge API to moveNodeByDelta to free moveNode for tree moves.
  • 2026-02-13: Refined undo/redo history behavior in packages/billing/src/components/invoice-designer/state/designerStore.ts:
    • Store now initializes history with a baseline snapshot so the first committed mutation can be undone.
    • setNodeProp / unsetNodeProp now commit to history by default to match existing property-edit behavior.
  • 2026-02-13: Introduced component schema definitions in packages/billing/src/components/invoice-designer/schema/componentSchema.ts:
    • Defines per-component label/description/category, defaults (size/layout/metadata), and hierarchy allowlists.
    • packages/billing/src/components/invoice-designer/constants/componentCatalog.ts now derives palette definitions from schema (schema is the new source of truth for palette metadata/defaults).
  • 2026-02-13: Hierarchy allowlists are now resolved via schema:
    • packages/billing/src/components/invoice-designer/state/hierarchy.ts is now a thin wrapper over getComponentSchema(type).hierarchy.
  • 2026-02-13: Palette insertion now uses schema defaults and generic tree ops:
    • packages/billing/src/components/invoice-designer/state/designerStore.ts addNodeFromPalette now pulls size/layout/style/metadata defaults from getComponentSchema(type).defaults and attaches nodes using insertChild semantics (patchOps.insertChild).
    • Default metadata normalization for repeated insertions (table columns, attachment items) moved into the store (normalizeDefaultMetadataForNewNode), removing hardcoded defaults from DesignerShell.tsx.
  • 2026-02-13: Outline + breadcrumbs now traverse the tree via children arrays:
    • packages/billing/src/components/invoice-designer/palette/OutlineView.tsx now renders from rootId and nodesById, deriving parent lookup only for expand behavior.
    • packages/billing/src/components/invoice-designer/DesignerShell.tsx breadcrumbs now derive parent links from childIds instead of relying on persisted parentId.
  • 2026-02-13: Selection/hover state now validates ids against nodesById:
    • packages/billing/src/components/invoice-designer/state/designerStore.ts selectNode/setHoverNode clear invalid ids and deleteNode clears selection/hover if the referenced node is removed as part of a subtree delete.
  • 2026-02-13: Canvas rendering now resolves from unified node props:
    • Added packages/billing/src/components/invoice-designer/utils/nodeProps.ts as the canonical accessor for props.name, props.layout, props.style, props.metadata (with temporary legacy fallbacks during cutover).
    • packages/billing/src/components/invoice-designer/canvas/DesignCanvas.tsx and packages/billing/src/components/invoice-designer/canvas/previewScaffolds.ts now render using props.* accessors.
    • Store mutations keep props in sync with legacy fields for now (packages/billing/src/components/invoice-designer/state/designerStore.ts).
  • 2026-02-13: Drag-drop reparent/reorder now uses generic tree ops:
    • packages/billing/src/components/invoice-designer/DesignerShell.tsx now calls store.moveNode(...) (tree move) instead of moveNodeToParentAtIndex.
    • packages/billing/src/components/invoice-designer/state/designerStore.flowDnd.test.ts updated to exercise moveNode(...) directly.
  • 2026-02-13: Resizing now writes through the generic patch API:
    • Removed updateNodeSize from packages/billing/src/components/invoice-designer/state/designerStore.ts.
    • packages/billing/src/components/invoice-designer/DesignerShell.tsx now implements resizeNode(...) using setNodeProp writes (size.*, baseSize.*, style.*) with a single history commit on mouse-up.
    • setNodeProp/unsetNodeProp now mirror name/style.*/layout.*/metadata.* updates into both legacy fields and props.* during cutover.
  • 2026-02-13: Started schema-driven Inspector cutover:
    • Added a minimal inspector schema format in packages/billing/src/components/invoice-designer/schema/inspectorSchema.ts and attached schemas to component definitions in packages/billing/src/components/invoice-designer/schema/componentSchema.ts.
    • Implemented packages/billing/src/components/invoice-designer/inspector/DesignerSchemaInspector.tsx which renders panels/fields from the selected node's component schema and writes edits via setNodeProp/unsetNodeProp (commit-on-blur for text fields).
    • Updated packages/billing/src/components/invoice-designer/DesignerShell.tsx to render the schema-driven inspector for supported metadata panels while leaving complex editors (tables/attachments/media) on the legacy path for now.
  • 2026-02-13: Expanded schema-driven Inspector field types and conditional panels:
    • packages/billing/src/components/invoice-designer/schema/inspectorSchema.ts now supports number, css-length, css-color field kinds plus visibleWhen rules (nodeIsContainer, pathEquals, parentPathEquals).
    • packages/billing/src/components/invoice-designer/schema/componentSchema.ts now defines a COMMON_INSPECTOR (Layout, Sizing, Flex Item) and merges it into all component schemas, so layout/style edits are schema-defined rather than hardcoded in the shell.
    • packages/billing/src/components/invoice-designer/DesignerShell.tsx removed the hardcoded Layout/Sizing/Flex Item inspector blocks and relies on DesignerSchemaInspector for those panels; legacy metadata editors remain only for complex types (table columns, attachment items, media).
  • 2026-02-13: Added first complex schema widget for metadata:
    • Implemented packages/billing/src/components/invoice-designer/inspector/widgets/TableEditorWidget.tsx and wired it into the schema via a widget inspector field (table-editor).
    • packages/billing/src/components/invoice-designer/schema/componentSchema.ts now attaches the table editor widget to both table and dynamic-table schemas.
    • Removed the hardcoded table/dynamic-table inspector block from packages/billing/src/components/invoice-designer/DesignerShell.tsx (tables now render their metadata editor via schema widget).
  • 2026-02-13: Updated designer <-> invoice-template AST mapping to prefer unified props + children:
    • packages/billing/src/components/invoice-designer/ast/workspaceAst.ts now reads name/layout/style/metadata via packages/billing/src/components/invoice-designer/utils/nodeProps.ts helpers and traverses children (fallback to childIds for legacy nodes).
    • Import now materializes props and children on generated nodes and keeps them in sync with legacy fields during cutover.
    • Updated packages/billing/src/components/invoice-designer/ast/workspaceAst.test.ts fixtures to set props + children so export/import exercises the unified tree.
  • 2026-02-13: Added undo/redo history regression test in packages/billing/src/components/invoice-designer/state/designerStore.undoRedo.test.ts:
    • Verifies tree state returns exactly to prior snapshots after a moveNode followed by deleteNode, via sequential undo() and redo().
  • 2026-02-13: Added schema invariants test in packages/billing/src/components/invoice-designer/schema/componentSchema.test.ts:
    • Ensures every component type has defaults, an inspector schema, and reciprocal nesting allowlists (parent allowedChildren aligns with child allowedParents).
  • 2026-02-13: Added repo/unit guard ensuring packages/billing/src/components/invoice-designer/state/hierarchy.ts stays deleted and is not imported anywhere in the invoice designer code.
  • 2026-02-13: Added palette insertion integration test in packages/billing/src/components/invoice-designer/state/designerStore.addNodeFromPalette.test.ts:
    • Verifies addNodeFromPalette pulls layout/metadata/size defaults from the component schema and attaches the new node id into the parent children array.
  • 2026-02-13: Added outline rendering integration test in packages/billing/src/components/invoice-designer/palette/OutlineView.integration.test.tsx:
    • Verifies outline order matches childIds ordering and selection state drives highlight styling.
  • 2026-02-13: Added breadcrumbs hierarchy integration coverage in packages/billing/src/components/invoice-designer/DesignerShell.breadcrumbs.test.ts:
    • Validates breadcrumb path is derived from childIds (children-only hierarchy) and does not depend on persisted parentId.
  • 2026-02-13: Added unified props rendering integration test in packages/billing/src/components/invoice-designer/canvas/DesignCanvas.unifiedProps.integration.test.tsx:
    • Ensures node.props.layout and node.props.style are sufficient to drive DOM styles (without relying on node.layout / node.style).
  • 2026-02-13: Added drag-drop reorder integration test in packages/billing/src/components/invoice-designer/state/designerStore.dragDropReorder.integration.test.ts:
    • Uses loadWorkspace({ rootId, nodesById }) to validate moveNode(...) reorders children for flow (flex) containers in the unified snapshot format.
  • 2026-02-13: Added drag-drop reparent integration test in packages/billing/src/components/invoice-designer/state/designerStore.dragDropMoveAcross.integration.test.ts:
    • Validates moving across containers updates only the unified children arrays in the exported workspace snapshot and rejects invalid parent targets via schema nesting rules.
  • 2026-02-13: Added resize-to-DOM integration test in packages/billing/src/components/invoice-designer/canvas/DesignCanvas.resizeProps.integration.test.tsx:
    • Simulates resize writes via store.setNodeProp('style.width'/'style.height', ...) and verifies the rendered canvas node element updates its inline sizing.
  • 2026-02-13: Persisted workspace snapshots now omit runtime geometry/editor-only props:
    • packages/billing/src/components/invoice-designer/state/designerStore.ts exportWorkspace() sanitizes node.props to drop position, size, baseSize, and layoutPresetId (new saves are unified-only).
    • Updated packages/billing/src/actions/invoicePreviewPdfParity.integration.test.ts workspace fixture to use { rootId, nodesById } (no nodes / constraints).
    • Updated packages/billing/src/components/invoice-designer/state/designerStore.constraints.test.ts assertions to validate unified snapshots.
  • 2026-02-13: Removed legacy hierarchy module:
    • Deleted packages/billing/src/components/invoice-designer/state/hierarchy.ts.
    • Moved getAllowedChildrenForType / getAllowedParentsForType / canNestWithinParent helpers into packages/billing/src/components/invoice-designer/schema/componentSchema.ts and updated call sites to import from schema.
  • 2026-02-13: Removed per-property store actions in favor of patch ops:
    • Deleted legacy store APIs: updateNodeName, updateNodeMetadata, updateNodeLayout, updateNodeStyle, setNodePosition, moveNodeByDelta, moveNodeToParentAtIndex.
    • Kept label text behavior by normalizing label name <-> metadata.text changes inside setNodeProp/unsetNodeProp (only when mutating name or metadata.*).
    • Fixed rootId indexing so exportWorkspace() snapshots can be round-tripped via loadWorkspace() even when legacy fixtures use non-canonical document ids.
    • Updated affected tests and UI call sites to use setNodeProp/unsetNodeProp exclusively.
  • 2026-02-13: Added deterministic unified-tree traversal helper and tests:
    • packages/billing/src/components/invoice-designer/state/designerAst.ts now exports traverseDesignerAstNodeIds.
    • Added packages/billing/src/components/invoice-designer/state/designerAst.test.ts.
  • 2026-02-13: Added patch ops unit coverage:
    • Added packages/billing/src/components/invoice-designer/state/patchOps.setNodeProp.test.ts to validate immutable deep dot-path updates.
    • Added packages/billing/src/components/invoice-designer/state/patchOps.unsetNodeProp.test.ts to validate cleanup behavior for empty objects.
    • Added packages/billing/src/components/invoice-designer/state/patchOps.insertChild.test.ts to validate deterministic child insertion ordering.
    • Added packages/billing/src/components/invoice-designer/state/patchOps.moveNode.test.ts to validate reorder/reparent semantics + cycle prevention.
    • Added packages/billing/src/components/invoice-designer/state/patchOps.deleteNode.test.ts to validate subtree deletion behavior.
  • 2026-02-13: Added schema-driven inspector integration coverage:
    • Added packages/billing/src/components/invoice-designer/inspector/DesignerSchemaInspector.integration.test.tsx to assert the inspector renders fields from schema and writes unified props.* via generic patch operations.
    • Testing gotcha: our shared <Input> doesnt reliably forward id to the native <input>, so tests query controls by placeholder rather than label association.
  • 2026-02-13: Extracted Inspector input normalizers and added unit coverage:
    • Added packages/billing/src/components/invoice-designer/inspector/normalizers.ts (+ normalizeString, normalizeCssLength, normalizeCssColor, normalizeNumber) and wired DesignerSchemaInspector to use them.
    • Added packages/billing/src/components/invoice-designer/inspector/normalizers.test.ts to validate canonicalization behavior (for example unitless CSS lengths become px, empty strings unset).
  • 2026-02-13: Added table editor schema widget integration coverage:
    • Added packages/billing/src/components/invoice-designer/inspector/TableEditorWidget.integration.test.tsx to assert the table-editor widget updates metadata.columns and the canvas preview header reacts to the updated columns list.
  • 2026-02-13: Made workspace <-> template AST export/import deterministic:
    • Updated packages/billing/src/components/invoice-designer/ast/workspaceAst.ts import to treat a single top-level AST section wrapper as the designer page node (avoids nested page->section wrapping on roundtrip).
    • Added export -> import -> export deterministic roundtrip assertion in packages/billing/src/components/invoice-designer/ast/workspaceAst.test.ts.
  • 2026-02-13: Added persisted workspace snapshot format coverage:
    • Added packages/billing/src/components/invoice-designer/state/designerStore.exportWorkspace.test.ts to ensure exportWorkspace() returns only { rootId, nodesById, snapToGrid, gridSize, showGuides, showRulers, canvasScale } and strips runtime/editor-only props keys (position, size, baseSize, layoutPresetId).
  • 2026-02-13: Added repo guards for refactor completeness:
    • Added packages/billing/src/components/invoice-designer/state/noLegacyApis.guard.test.ts to enforce removed per-property store action names stay gone, and to keep hierarchy traversal using children (no childIds usage outside state/).
    • Updated packages/billing/src/components/invoice-designer/palette/OutlineView.tsx, packages/billing/src/components/invoice-designer/canvas/DesignCanvas.tsx, and packages/billing/src/components/invoice-designer/DesignerShell.tsx to traverse children rather than childIds.