Compare commits

...

2 Commits

Author SHA1 Message Date
270806539c Add ui-structure.md — full UI inventory and widget taxonomy
Covers all 7 nav sections with layout descriptions, all 11 dialog types,
recurring widget patterns (split pane, tabbed view, table, gallery, card,
field editor, modal dialog, non-modal overlay, full-page sub-view, canvas,
lightbox) with every instance listed, complete template inventory (41 entries),
and a primitive taxonomy for a future higher-level UI representation.
Linked from CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 14:18:09 +00:00
5fe7273e35 Update future-plans: dual event bus, UI sub-project boundary, fix duplicate
- Replace simple SSE note with full dual-bus architecture:
  server EventEmitter + client pub/sub, SSE as bridge, effects/ pattern,
  mutation wrapper to avoid wildcard, collection-level event granularity
- Add 'UI as self-contained sub-project' section: composition root pattern,
  main.mjs vs mock-main.mjs entry points, mock-api contract, views-never-
  call-each-other discipline
- Expand app.mjs monolith note to mention mount() export pattern
- Remove duplicate CodeMirror paragraph (copy-paste artifact)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 14:12:28 +00:00
3 changed files with 446 additions and 19 deletions

View File

@@ -378,6 +378,14 @@ git config user.email 'mikaels.claude.agent@efforting.tech'
--- ---
## UI structure
See [`ui-structure.md`](ui-structure.md) for the full inventory of sections,
widget patterns, dialogs, templates, and a taxonomy of primitives relevant to
designing a higher-level UI representation.
---
## Known limitations / planned rewrite notes ## Known limitations / planned rewrite notes
See [`future-plans.md`](future-plans.md) for full detail. Key points relevant See [`future-plans.md`](future-plans.md) for full detail. Key points relevant

View File

@@ -23,20 +23,52 @@ undo, audit trails, and debugging data corruption.
## Real-time / live updates ## Real-time / live updates
### Server-sent events for data changes ### Dual event bus architecture
When data changes on the server (upload, edit, delete — from any client), connected
browsers should receive a notification and update the affected view automatically.
Use case: uploading photos from a phone while the desktop browser has the Images or
Bins section open.
Server-sent events (SSE) are the natural fit — lightweight, one-directional, no Two independent buses, bridged by SSE:
library needed. The server emits a change event with a `type` (e.g. `source_images`,
`bins`) and the client re-fetches and re-renders only the affected collection.
Views that aren't currently visible don't need to do anything — they'll reload on
next navigation.
Ties directly into the delta tracking plan: the same write path that appends to the **Server bus** (`lib/bus.mjs`, Node `EventEmitter`):
delta log can also fan out to connected SSE clients. - Route handlers emit a specific event after each mutation (`field:deleted`,
`bin:changed`, etc.) and nothing else — side effects are not inline
- Effect modules in `server/effects/` subscribe and handle cascading work:
- `field-effects.mjs` — strips deleted field from all components/bins/bin types
- `sse-effects.mjs` — broadcasts mutations to connected SSE clients
- `audit-effects.mjs` — writes delta log (future)
- `bin-effects.mjs` — e.g. propagates type dimension changes to bins
- New cross-cutting concerns (audit, cache invalidation, notifications) are
additional listeners — route handlers never grow
**Client bus** (`lib/bus.mjs`, lightweight pub/sub or `EventTarget`):
- `api.mjs` emits on the bus after every successful mutation
- `sse.mjs` (SSE connection client) translates incoming server events to bus emits
- View modules subscribe to relevant events and re-render; they never call each other
- `mock-api.mjs` also emits on the same bus after in-memory mutations, so views
react correctly in mock mode without any SSE
**SSE bridge**: `sse-effects.mjs` on the server broadcasts to connected clients;
`sse.mjs` on the client receives and re-emits on the client bus. Views are unaware
of whether a change was local or remote.
**Avoiding wildcard listeners**: instead of a wildcard `*` listener (not natively
supported by `EventEmitter`), emit a generic `mutation` event alongside every
specific event. The SSE broadcaster listens to `mutation`; everything else listens
to specific events. New event types are automatically forwarded without touching
the broadcaster.
```js
function emit(event, data) {
bus.emit(event, data);
bus.emit('mutation', { event, data });
}
```
**Event granularity**: collection-level events are sufficient (`bins:changed`,
`components:changed`). Passing the affected id or record is optional — views can
use it to do a targeted update or ignore it and re-fetch the collection. Fine-grained
events are an optimisation to add later if full-collection re-fetches become slow.
Ties into the delta tracking plan: `audit-effects.mjs` is another bus listener —
the same mutation path that drives SSE also drives the delta log.
## App architecture ## App architecture
@@ -69,10 +101,39 @@ const SECTION_RENDERERS = {
function render() { sync_nav(); SECTION_RENDERERS[section]?.(); } function render() { sync_nav(); SECTION_RENDERERS[section]?.(); }
``` ```
### UI as a self-contained sub-project
The UI boundary is `api.mjs` — every piece of data the UI touches goes through
named exports in that file. This seam should be made explicit so the UI can be
developed and tested against a mock without a running server.
**Composition root / dependency injection**: `app.mjs` should not import `api.mjs`
directly. Instead it receives the api implementation as a parameter. Two thin entry
files wire it up:
```
main.mjs — imports real api.mjs, passes to app.start()
mock-main.mjs — imports mock-api.mjs, passes to app.start()
```
`mock-main.mjs` is a separate deployable (e.g. served at `/mock` or on a dev port),
not a URL flag. The app has no runtime knowledge of which implementation it received.
**mock-api.mjs**: same exports as `api.mjs`, backed by in-memory arrays seeded with
realistic fixture data. Mutations update the in-memory state so the UI behaves
realistically (add/delete/edit all persist within the session). Also emits on the
client bus so cross-view reactivity works identically to the real app. No SSE
connection needed in mock mode — the bus events come from the mock mutations.
**Views never call each other**: once split into modules, `views/bins.mjs` must
not import `views/inventory.mjs`. Cross-section reactions happen exclusively through
the client bus. This is the main structural discipline that makes the split work.
### app.mjs monolith ### app.mjs monolith
`app.mjs` is large. Consider splitting into per-section modules `app.mjs` is large. Split into per-section view modules (`views/components.mjs`,
(`views/components.mjs`, `views/grids.mjs`, etc.) that each export their render `views/grids.mjs`, `views/bins.mjs`, etc.) each owning its local state, subscribing
function and own their local state. to bus events at init, and exporting a single `mount(container)` function. The
composition root (`main.mjs`) imports all view modules and registers them.
### Split CSS into per-section files ### Split CSS into per-section files
`style.css` is a single large file and getting hard to navigate. Split into `style.css` is a single large file and getting hard to navigate. Split into
@@ -328,10 +389,6 @@ slot targets) in addition to custom formatters.
This revision also applies to field parsers and search view expressions once those This revision also applies to field parsers and search view expressions once those
exist — they all follow the same pattern of JS function → structured output → exist — they all follow the same pattern of JS function → structured output →
context-specific renderer. context-specific renderer.
Any field that accepts JavaScript (name formatter templates, future custom search
views, field parsers, etc.) should use a CodeMirror 6 editor instead of a plain
`<textarea>`. Gives syntax highlighting, bracket matching, and a proper editing
experience for JS snippets.
## Search & views ## Search & views

362
ui-structure.md Normal file
View File

@@ -0,0 +1,362 @@
# UI Structure
Current UI inventory — sections, widgets, templates, and recurring patterns.
Intended as a reference for the rewrite and for defining a higher-level UI
representation.
---
## Navigation
Top bar with 7 section buttons + a maintenance dropdown (⚙):
| Button | Section | Default? |
|--------|---------|----------|
| Components | Split-pane list/detail | ✓ |
| Inventory | Table | |
| Fields | Table | |
| Grids | Tabbed (Grids / Source images) + sub-views | |
| Templates | Card list | |
| Bins | Tabbed (Bins / Source images / Types) | |
| Images | Admin list | |
Maintenance dropdown (not a section):
- Generate missing PDF thumbnails
- Remove orphaned source image entries
---
## Sections
### Components
**Layout:** Resizable split-pane (width persisted in localStorage)
**Left — master list:**
- Search input (filters by name + field values)
- Quick-add input (creates component inline)
- List of `t-component-row` items (name + colored field-value badge tags)
**Right — detail panel:**
- Placeholder when nothing selected
- When selected: name, description, Edit / Duplicate / Delete actions
- Fields block — name/value rows
- Images block — thumbnail gallery + upload button
- Files block — linked PDFs + file picker button
- Inventory block — location entries (each with edit/delete) + add button
**Dialogs opened from here:** component edit, field create, inventory entry, file
picker, confirm delete
---
### Inventory
**Layout:** Toolbar + table
- Filter: text search + location-type dropdown (All / Physical / BOM / Digital / Grid)
- Table columns: Component | Type | Location/Ref | Qty | Notes | Actions (edit, delete)
- Component name cells are links → `/components/:id`
- Grid location cells link to the grid viewer at that cell
- "+ Add entry" button in toolbar
**Dialogs opened from here:** inventory entry edit/create, confirm delete
---
### Fields
**Layout:** Toolbar + table
- Table columns: Name | Unit | Description | Actions (edit, delete)
- "+ Add field" button in toolbar
**Dialogs opened from here:** field edit/create, confirm delete
---
### Templates
**Layout:** Toolbar + card list
- Cards show: name, formatter JS code (read-only `<pre>`), edit/delete actions
- "+ Add template" button
- Live preview panel in the edit dialog
**Dialogs opened from here:** template edit/create, confirm delete
---
### Grids
**Layout:** Tabbed, plus three full-page sub-views (not dialogs)
**Tab: Grids**
- Grid cards (image grid preview, name, delete)
- Draft cards (unprocessed setups, badge "Draft")
- "+ New grid" button → wizard dialog
**Tab: Source images**
- Source image gallery (`t-source-card`)
- "+ Upload" button
**Sub-view: Panel manager** (replaces main content)
- Editable grid of panel slots — each slot is a photo region
- Header with grid name, Cancel / Process buttons
**Sub-view: Grid setup** (replaces main content)
- Canvas corner editor (`Grid_Setup` class) with pan/zoom
- Right panel: cell size info, action buttons, progress
**Sub-view: Grid viewer** (replaces main content)
- Full grid of cell thumbnails
- Click cell → cell inventory overlay (non-modal panel)
- Header with Edit panels / Delete actions
**Dialogs opened from here:** new grid wizard, source picker, confirm delete
---
### Bins
**Layout:** Tabbed
**Tab: Bins**
- Gallery of `t-bin-card` items (processed image or "Not processed" indicator)
- Click Edit (✎) → bin editor dialog
**Tab: Source images**
- Filtered source gallery (uses includes 'bin')
- Each card has a "+ Bin" button to create a bin from that source
**Tab: Types**
- List of `t-bin-type-row` items (name, dimensions, description)
- Edit / delete per row
- "+ Add type" button
**Dialogs opened from here:** bin editor, bin type edit/create, bin content
add/edit, field create, confirm delete
---
### Images
**Layout:** Admin list
- One row per source image: thumbnail, filename, dimensions, uses checkboxes
(grid / bin, toggleable), delete button
- Purpose: correct mislabeled source images
---
## Widget Patterns
These are the recurring structural widgets across the app. Listing all instances
makes the taxonomy visible and suggests what a higher-level UI DSL would need to
express.
---
### Split pane (master/detail)
A resizable two-column layout. Left = filterable list. Right = detail view of
selected item, or a placeholder.
| Instance | Left | Right |
|----------|------|-------|
| Components | Component list + search | Detail panel (fields, images, files, inventory) |
Currently only one instance. The pattern is general enough to reuse for grids,
bins, etc.
---
### Tabbed section
A tab bar that switches between content panels within the same section. Tab state
can be reflected in the URL (`/bins/sources`, `/grids/sources`).
| Instance | Tabs |
|----------|------|
| Grids section | Grids \| Source images |
| Bins section | Bins \| Source images \| Types |
| Bin editor dialog | Fields \| Contents |
---
### Table view
Toolbar (search/filter + action button) + `<table>` with rows and per-row
edit/delete icon buttons.
| Instance | Row content |
|----------|-------------|
| Inventory | Component, type badge, location ref, qty, notes |
| Fields | Name, unit, description |
---
### Card gallery
A wrapping grid of cards. Each card has a visual element (image or preview),
a title, and action buttons.
| Instance | Card type | Visual |
|----------|-----------|--------|
| Grid list | `t-grid-card` | Multi-thumbnail preview |
| Bin gallery | `t-bin-card` | Processed image or placeholder |
| Source image gallery | `t-source-card` | Photo thumbnail |
| Template list | `t-template-card` | Formatter code `<pre>` |
---
### List row with actions
A horizontal row with info spans on the left and icon buttons on the right. Used
in data-table rows and standalone list elements.
| Instance | Info shown |
|----------|------------|
| Inventory table rows | Component, type, location, qty, notes |
| Field table rows | Name, unit, description |
| Bin type rows | Name, dimensions, description |
| Bin content rows | Name/component, qty, notes |
| Image admin rows | Thumbnail, filename, dimensions, uses checkboxes |
---
### Field editor (dynamic rows)
A list of label + text input + remove button rows, plus an "add field" dropdown
and "New…" button. Shared across component edit, bin edit, and bin type edit via
`build_field_editor()`.
| Instance | Host |
|----------|------|
| Component edit dialog | `#c-field-rows` |
| Bin editor — Fields tab | `#bin-field-rows` |
| Bin type dialog | `#bt-field-rows` |
---
### Modal dialog
`<dialog>` element shown with `.showModal()`. All injected at init from
`<template>` elements. Closed via Cancel button, form submit, or backdrop click.
Save callback stored as a module-level variable, called by the registered submit
handler.
| Dialog | Purpose | Key inputs |
|--------|---------|------------|
| `dialog-component` | Create/edit component | Name, description, field editor |
| `dialog-inventory` | Create/edit inventory entry | Component, type, location, qty, notes, grid picker |
| `dialog-field` | Create/edit field definition | Name, unit, description |
| `dialog-template` | Create/edit name formatter | Name, JS code, test data, live preview |
| `dialog-new-grid` | Grid creation wizard | Name, rows, cols, photo coverage |
| `dialog-source-picker` | Pick a source image | Upload or select from gallery |
| `dialog-confirm` | Generic confirm/delete | Dynamic message text |
| `dialog-file-picker` | Link a PDF to a component | PDF list, upload section |
| `dialog-bin-editor` | Edit bin (image, corners, fields, contents) | Name, type, image/canvas, tabs |
| `dialog-bin-type` | Create/edit bin type | Name, dimensions, description, field editor |
| `dialog-bin-content` | Add/edit bin content item | Type (component/free text), qty, notes |
---
### Non-modal overlay / panel
Injected into the DOM and shown/hidden with `hidden`. Not a `<dialog>` — does not
block the rest of the UI.
| Instance | Trigger | Content |
|----------|---------|---------|
| Cell inventory overlay | Click grid cell in viewer | Inventory entries for that cell, + add button |
---
### Full-page sub-view (section replacement)
Replaces the entire `<main>` content. Not a dialog or overlay — the user is
"inside" a different mode of the same section. Back navigation returns to the
section list view.
| Instance | Entered from | Content |
|----------|-------------|---------|
| Grid setup | New grid wizard → source picker | Canvas corner editor + controls |
| Panel manager | Grid card or setup → next | Editable panel slot grid |
| Grid viewer | Grid card click | Cell thumbnail grid, cell inventory overlay |
---
### Canvas editor
`Grid_Setup` class renders into a `<canvas>`. Handles pan (middle mouse), zoom
(wheel), and corner/edge handle dragging for perspective correction. Used in two
places with identical behaviour.
| Instance | Embedded in |
|----------|-------------|
| Grid setup sub-view | `t-grid-setup` canvas |
| Bin corner editor | `dialog-bin-editor` (revealed on "Adjust corners") |
---
### Image lightbox
Full-screen image overlay triggered by clicking thumbnails. Single global
instance, not per-section. Clicking anywhere closes it.
---
## Complete Template Inventory
| Template ID | Type | Renders |
|-------------|------|---------|
| `t-section-components` | Section | Split-pane container |
| `t-section-inventory` | Section | Table container |
| `t-section-fields` | Section | Table container |
| `t-section-templates` | Section | Card list container |
| `t-section-grids` | Section | Tabbed container |
| `t-section-bins` | Section | Tabbed container |
| `t-section-images` | Section | Admin list container |
| `t-component-row` | List item | Name + field-value badge tags |
| `t-field-tag` | Badge | Single field name + value |
| `t-detail-placeholder` | Placeholder | "Select a component" message |
| `t-detail-content` | Detail panel | Full component detail |
| `t-detail-field-row` | Row | Field name + rendered value |
| `t-detail-inv-entry` | Row | Inventory entry with images sub-gallery |
| `t-image-thumb` | Thumbnail | Image link + delete button |
| `t-inventory-row` | Table row | Inventory entry (all columns) |
| `t-field-row` | Table row | Field definition (all columns) |
| `t-empty-row` | Table empty | colspan message cell |
| `t-empty-block` | Block empty | Div with message |
| `t-template-card` | Card | Template name + code + actions |
| `t-source-card` | Card | Source image + meta + uses badges |
| `t-draft-card` | Card | Draft grid + badge + actions |
| `t-grid-card` | Card | Grid preview thumbs + meta + actions |
| `t-panel-manager` | Sub-view | Panel slot editor grid |
| `t-panel-slot` | Grid cell | Slot thumbnail + label |
| `t-grid-setup` | Sub-view | Canvas + controls |
| `t-grid-viewer` | Sub-view | Cell thumbnail grid |
| `t-grid-cell` | Grid cell | Cell image + location label |
| `t-bin-card` | Card | Bin image or placeholder + actions |
| `t-bin-type-row` | Row | Bin type info + actions |
| `t-bin-content-row` | Row | Content item info + actions |
| `t-img-admin-row` | Row | Source image admin row |
| `t-dialog-component` | Dialog | Component create/edit |
| `t-dialog-inventory` | Dialog | Inventory entry create/edit |
| `t-dialog-field` | Dialog | Field definition create/edit |
| `t-dialog-template` | Dialog | Template create/edit + preview |
| `t-dialog-new-grid` | Dialog | Grid creation wizard |
| `t-dialog-source-picker` | Dialog | Source image selector |
| `t-dialog-confirm` | Dialog | Generic confirm |
| `t-dialog-file-picker` | Dialog | PDF picker + upload |
| `t-dialog-bin-editor` | Dialog | Bin edit (image, corners, fields, contents) |
| `t-dialog-bin-type` | Dialog | Bin type create/edit |
| `t-dialog-bin-content` | Dialog | Bin content item create/edit |
| `t-cell-inventory` | Overlay | Grid cell inventory entries |
---
## Notes for a Higher-Level UI Representation
The widgets above map fairly cleanly onto a small set of primitives that a UI DSL
would need:
- **Section** — a top-level navigable view, registered in the nav bar
- **Tabs** — switch between named content panels, state optionally in URL
- **SplitPane** — resizable master/detail, left = list, right = detail
- **Table** — toolbar + rows, each row has a schema and action set
- **Gallery** — wrapping grid of cards, each card has a schema
- **Row** — horizontal item with info fields and icon actions
- **FieldEditor** — dynamic key/value input list (add/remove/edit)
- **Dialog** — modal form with a save callback, injected from a template
- **Overlay** — non-modal panel, shown/hidden in place
- **SubView** — full-page mode that replaces main content
- **Canvas** — bespoke interactive widget (not expressible declaratively)
- **Lightbox** — global full-screen image viewer
Most sections are composed of 23 of these primitives. The Components section
(SplitPane → Table + Gallery + FieldEditor) is the most complex. The canvas-based
views are the only things that require imperative escape hatches.