From a80e99e500587aec53689019a2f4e20269949410 Mon Sep 17 00:00:00 2001 From: forbes-0023 Date: Wed, 11 Feb 2026 10:40:09 -0600 Subject: [PATCH] docs: update specs for schema-driven form descriptor API - frontend-spec.md: rewrite CreateItemPane spec for dynamic form rendering from form descriptor, replace CategoryPicker three-column spec with multi-stage domain/subcategory picker, replace useCategories hook with useFormDescriptor, update form sections to dynamic field groups, mark hierarchical categories as implemented, remove sourcing_link/standard_cost from item-level DB columns, update types and implementation order - SPECIFICATION.md: rename /api/schemas/{name}/properties endpoint to /api/schemas/{name}/form --- docs/SPECIFICATION.md | 2 +- frontend-spec.md | 339 +++++++++++++++++++++++------------------- 2 files changed, 185 insertions(+), 156 deletions(-) diff --git a/docs/SPECIFICATION.md b/docs/SPECIFICATION.md index 8f89a93..772b927 100644 --- a/docs/SPECIFICATION.md +++ b/docs/SPECIFICATION.md @@ -627,7 +627,7 @@ POST /api/uploads/presign # Get presigned MinI # Schemas (read: viewer, write: editor) GET /api/schemas # List all schemas GET /api/schemas/{name} # Get schema details -GET /api/schemas/{name}/properties # Get property schema for category +GET /api/schemas/{name}/form # Get form descriptor (field groups, widgets, category picker) POST /api/schemas/{name}/segments/{segment}/values # Add enum value [editor] PUT /api/schemas/{name}/segments/{segment}/values/{code} # Update enum value [editor] DELETE /api/schemas/{name}/segments/{segment}/values/{code} # Delete enum value [editor] diff --git a/frontend-spec.md b/frontend-spec.md index 4e29629..6eb6ab3 100644 --- a/frontend-spec.md +++ b/frontend-spec.md @@ -1,6 +1,6 @@ # Silo Frontend Specification -Current as of 2026-02-08. Documents the React + Vite + TypeScript frontend (migration from Go templates is complete). +Current as of 2026-02-11. Documents the React + Vite + TypeScript frontend (migration from Go templates is complete). ## Overview @@ -68,6 +68,7 @@ web/ │ └── AuthContext.tsx AuthProvider with login/logout/refresh methods ├── hooks/ │ ├── useAuth.ts Context consumer hook + │ ├── useFormDescriptor.ts Fetches form descriptor from /api/schemas/{name}/form (replaces useCategories) │ ├── useItems.ts Items fetching with search, filters, pagination, debounce │ └── useLocalStorage.ts Typed localStorage persistence hook ├── styles/ @@ -271,63 +272,81 @@ Vite dev server runs on port 5173 with proxy config in `vite.config.ts` forwardi ## New Frontend Tasks -# CreateItemPane Redesign Specification +# CreateItemPane — Schema-Driven Dynamic Form -**Date**: 2026-02-06 -**Scope**: Replace existing `CreateItemPane.tsx` with a two-column layout, multi-stage category picker, file attachment via MinIO, and full use of screen real estate. +**Date**: 2026-02-10 +**Scope**: `CreateItemPane.tsx` renders a dynamic form driven entirely by the form descriptor API (`GET /api/schemas/{name}/form`). All field groups, field types, widgets, and category-specific fields are defined in YAML and resolved server-side. **Parent**: Items page (`ItemsPage.tsx`) — renders in the detail pane area per existing in-pane CRUD pattern. --- ## Layout -The pane uses a CSS Grid two-column layout instead of the current single-column form: +Single-column scrollable form with a green header bar. Field groups are rendered dynamically from the form descriptor. Category-specific field groups appear after global groups when a category is selected. ``` -┌──────────────────────────────────────────────────────┬──────────────┐ -│ Header: "New Item" [green bar] Cancel │ Create │ │ -├──────────────────────────────────────────────────────┤ │ -│ │ Auto- │ -│ ── Identity ────────────────────────────────────── │ assigned │ -│ [Part Number *] [Type * v] │ metadata │ -│ [Description ] │ │ -│ Category * [Domain │ Group │ Subtype ] │──────────────│ -│ Mechanical│ Structural│ Bracket │ │ │ -│ Electrical│ Bearings │ Plate │ │ Attachments │ -│ ... │ ... │ ... │ │ ┌─ ─ ─ ─ ┐ │ -│ ── Sourcing ────────────────────────────────────── │ │ Drop │ │ -│ [Sourcing Type v] [Standard Cost $ ] │ │ zone │ │ -│ [Unit of Measure v] [Sourcing Link ] │ └─ ─ ─ ─ ┘ │ -│ │ file.FCStd │ -│ ── Details ─────────────────────────────────────── │ drawing.pdf │ -│ [Long Description ] │ │ -│ [Projects: [tag][tag] type to search... ] │──────────────│ -│ │ Thumbnail │ -│ │ [preview] │ -└──────────────────────────────────────────────────────┴──────────────┘ +┌──────────────────────────────────────────────────────────────────────┐ +│ Header: "New Item" [green bar] Cancel │ Create │ +├──────────────────────────────────────────────────────────────────────┤ +│ │ +│ Category * [Domain buttons: F C R S E M T A P X] │ +│ [Subcategory search + filtered list] │ +│ │ +│ ── Identity ────────────────────────────────────────────────────── │ +│ [Type * (auto-derived from category)] [Description ] │ +│ │ +│ ── Sourcing ────────────────────────────────────────────────────── │ +│ [Sourcing Type v] [Manufacturer] [MPN] [Supplier] [SPN] │ +│ [Sourcing Link] │ +│ │ +│ ── Cost & Lead Time ────────────────────────────────────────────── │ +│ [Standard Cost $] [Lead Time Days] [Min Order Qty] │ +│ │ +│ ── Status ──────────────────────────────────────────────────────── │ +│ [Lifecycle Status v] [RoHS Compliant ☐] [Country of Origin] │ +│ │ +│ ── Details ─────────────────────────────────────────────────────── │ +│ [Long Description ] │ +│ [Projects: [tag][tag] type to search... ] │ +│ [Notes ] │ +│ │ +│ ── Fastener Specifications (category-specific) ─────────────────── │ +│ [Material] [Finish] [Thread Size] [Head Type] [Drive Type] ... │ +│ │ +└──────────────────────────────────────────────────────────────────────┘ ``` -Grid definition: `grid-template-columns: 1fr 320px`. The left column scrolls independently if content overflows. The right sidebar is a flex column with sections separated by `--ctp-surface1` borders. +## Data Source — Form Descriptor API + +All form structure is fetched from `GET /api/schemas/kindred-rd/form`, which returns: + +- `category_picker`: Multi-stage picker config (domain → subcategory) +- `item_fields`: Definitions for item-level fields (description, item_type, sourcing_type, etc.) +- `field_groups`: Ordered groups with resolved field metadata (Identity, Sourcing, Cost, Status, Details) +- `category_field_groups`: Per-category-prefix groups (e.g., Fastener Specifications for `F` prefix) +- `field_overrides`: Widget hints (currency, url, select, checkbox) + +The YAML schema (`schemas/kindred-rd.yaml`) is the single source of truth. Adding a new field or category in YAML propagates to all clients with no code changes. ## File Location -`web/src/components/items/CreateItemPane.tsx` (replaces existing file) +`web/src/components/items/CreateItemPane.tsx` -New supporting files: +Supporting files: | File | Purpose | |------|---------| -| `web/src/components/items/CategoryPicker.tsx` | Multi-stage category selector | +| `web/src/components/items/CategoryPicker.tsx` | Multi-stage domain/subcategory selector | | `web/src/components/items/FileDropZone.tsx` | Drag-and-drop file upload with MinIO presigned URLs | | `web/src/components/items/TagInput.tsx` | Multi-select tag input for projects | -| `web/src/hooks/useCategories.ts` | Fetches category tree from schema data | +| `web/src/hooks/useFormDescriptor.ts` | Fetches and caches form descriptor from `/api/schemas/{name}/form` | | `web/src/hooks/useFileUpload.ts` | Manages presigned URL upload flow | ## Component Breakdown ### CreateItemPane -Top-level orchestrator. Manages form state, submission, and layout. +Top-level orchestrator. Renders dynamic form from the form descriptor. **Props** (unchanged interface): @@ -341,68 +360,64 @@ interface CreateItemPaneProps { **State**: ```typescript -const [form, setForm] = useState({ - part_number: '', - item_type: 'part', - description: '', - category_path: [], // e.g. ['Mechanical', 'Structural', 'Bracket'] - sourcing_type: 'manufactured', - standard_cost: '', - unit_of_measure: 'ea', - sourcing_link: '', - long_description: '', - project_ids: [], -}); -const [attachments, setAttachments] = useState([]); -const [thumbnail, setThumbnail] = useState(null); +const { descriptor, categories, loading } = useFormDescriptor(); +const [category, setCategory] = useState(''); // selected category code, e.g. "F01" +const [fields, setFields] = useState>({}); // all field values keyed by name const [error, setError] = useState(null); const [submitting, setSubmitting] = useState(false); ``` +A single `fields` record holds all form values (both item-level and property fields). The `ITEM_LEVEL_FIELDS` set (`description`, `item_type`, `sourcing_type`, `long_description`) determines which fields go into the top-level request vs. the `properties` map on submission. + +**Auto-derivation**: When a category is selected, `item_type` is automatically set based on the `derived_from_category` mapping in the form descriptor (e.g., category prefix `A` → `assembly`, `T` → `tooling`, default → `part`). + +**Dynamic rendering**: A `renderField()` function maps each field's `widget` type to the appropriate input: + +| Widget | Rendered As | +|--------|-------------| +| `text` | `` | +| `number` | `` | +| `textarea` | `