# Future Plans ## KV store ### Extract into shared library `kv-store.mjs` is already copied into at least 3 projects (`electronics-inventory`, `fs-views`, `publication-tool`). Should live in its own Gitea repo as an installable npm package (`npm install git+https://...`) so changes propagate rather than drift. ### Delta / revision tracking Add a delta log alongside the main snapshot file (e.g. `inventory.ndjson.deltas`) that records every `set`/`delete` as a timestamped entry. The main file stays a clean current-state snapshot; the delta file accumulates the full history. Enables undo, audit trails, and debugging data corruption. ## App architecture ### parse_url mutates too many module-level variables `parse_url()` directly assigns to a large number of module-level state variables (`section`, `grid_view_state`, `grid_tab`, `current_grid_id`, `grid_draft`, `current_panel_idx`, `grid_source_id`, `highlight_cell`, `selected_component_id`). This is fragile and hard to reason about. Preferred direction: represent the full UI state as a single immutable state object, and have `parse_url()` return a new state value rather than mutating globals: ```js function parse_url(path) { return { section, grid_view_state, current_grid_id, ... }; } state = parse_url(location.pathname); render(state); ``` ### render() if/else chain The render dispatcher is a long chain of bare `else if` branches. Replace with a lookup table: ```js const SECTION_RENDERERS = { components: render_components, inventory: render_inventory, fields: render_fields, grids: render_grids, templates: render_templates, }; function render() { sync_nav(); SECTION_RENDERERS[section]?.(); } ``` ### app.mjs monolith `app.mjs` is large. Consider splitting into per-section modules (`views/components.mjs`, `views/grids.mjs`, etc.) that each export their render function and own their local state. ### DRY / SSoT audit As the app grows, patterns are being duplicated rather than centralized. Areas to review: - Field sorting: same sort-by-name logic appears in both detail view and edit dialog - Field rendering: `render_field_value()` exists but call sites still sometimes inline display logic - Component display name: `component_display_name()` is the SSoT but there may be call sites that still use `c.name` directly - Server-side: PDF conflict checks, sanitize calls, and rename logic are inline in route handlers — could be extracted into a `pdf_service` helper - General pass to identify and eliminate copy-paste between routes and between render functions before the codebase grows further ## Field system ### Improvements #### Field display in component detail should use a table Currently rendered as CSS grid rows but columns don't align because each row is independent. Use an actual `` so name and value columns line up across all fields. This is tabular data and a table is the right element. #### Field value parser chain Similar to how name formatters use a template chain, field values could be passed through a parser chain that returns structured data based on field name/type hints. Examples: - A field whose name contains `tolerance` could parse `20` as `{ negative: 20, positive: 20 }` and `-10/+30` as `{ negative: 10, positive: 30 }` - URL detection (currently hardcoded in `render_field_value()`) could be one parser in this chain rather than a special case - Mouser/Digi-Key part numbers could be detected and return a structured link target The parser chain would mirror the template system: user-defined or built-in parsers keyed by field name pattern, tried in order, returning structured data or `null` to pass through to the next. `render_field_value()` would then receive parsed data and render accordingly. #### Field rendering integrations With or without a parser chain, `render_field_value()` should gain: - Mouser/Digi-Key part number fields → auto-craft links to product pages - More URL-like patterns (without `https://` prefix) #### Field selector filter When adding a field to a component in the edit dialog, the dropdown becomes unwieldy with many fields. Add a filter/search input to the field selector. #### Parametric search Allow searching/filtering components by field values, not just names. Examples: - `resistance < 10k`, `package = 0603`, `voltage_rating >= 50` - Cross-field queries: find all 0603 resistors under 10kΩ - Should integrate with the existing word-split search or replace it with a richer query language - Depends on field types (numeric vs string) for range queries to work correctly ### Long term #### Field grouping / linkage Some fields naturally belong together (e.g. `frequency_stability` and `frequency_stability_temp_range`). Options: - Soft linkage: tag fields with a group name, display grouped in the UI - Structured fields: a field can be a record type with named sub-fields (e.g. `stability: { value: 10, unit: "ppm", temp_low: -40, temp_high: 85 }`) Structured records are the more powerful option but require a schema system and more complex UI. Grouping/linkage is a lighter short-term win. #### Renderer/parser result cache Once parsers and formatters run per-render, a cache keyed on field value + template version would avoid redundant work on large inventories. Invalidated when any template changes. Not urgent — premature until the parser chain exists. #### Field types Currently all field values are free-text strings. Typed fields (numeric, enum/dropdown) would enable better formatting, validation, and range-aware search. Prerequisite for parametric search with range operators. #### Measurement dimensions and unit conversion Instead of a bare unit string on a field, associate a field with a measurement dimension (e.g. `temperature`, `resistance`, `frequency`, `voltage`). The dimension defines the set of valid units and the conversion factors between them (°C, °K, °R, °F for temperature; Ω, kΩ, MΩ for resistance; etc.). SI prefixes (k, M, µ, n, p, etc.) are not separate units — they are a presentation layer on top of a unit. `25kΩ` should be stored as `{ value: "25", prefix: "k", unit: "Ω" }` — preserving the original string value and prefix exactly as entered, so no precision or notation is lost. A canonical numeric form is derived from the stored triple only when needed for comparison or search queries (e.g. `R < 10k` → compare canonical floats). Display always reconstructs from the stored `value + prefix + unit`, so `4k7` stays `4k7` and `25.0` stays `25.0`. This would allow: - Lossless storage of entered values (significant digits, notation style preserved) - Parametric search with cross-prefix comparisons via derived canonical values - Unit conversion on query (e.g. `temp > 200K` matching a stored `-73°C`) - Catching unit mismatches at entry time ## Deployment ### Read-only public mode A runtime flag (e.g. `READ_ONLY=1`) that starts the server in a read-only mode suitable for public-facing deployment: - All write API endpoints disabled (POST/PUT/DELETE return 403) - UI hides all edit controls, dialogs, and maintenance actions - Data served directly from the same `data/` directory This allows a simple deployment workflow: rsync the `data/` directory from the private instance to a public server running in read-only mode. No database sync, no separate export step. ## Search & views ### Custom search views Saved searches defined as JS expressions (similar to the template system), evaluated against each component to produce a filtered and optionally transformed list. Example use cases: - "all components with a non-empty `todo` field" - "all SMD resistors with no datasheet attached" - "all components missing a `package` field" Views would be named, saved, and accessible from the nav or a dedicated views section. The expression receives the full component object and returns truthy to include it. Could later be extended to also control sort order and displayed columns. ## PDF / files ### Auto-select file after upload in file picker When uploading a PDF from within the file picker (opened from a component), the newly uploaded file should be automatically linked to that component without requiring a manual "Select" click. ### File picker search filter The file picker dialog has no search/filter input. With many datasheets this becomes unwieldy. Add a filter input at the top of the list that narrows by display name and filename. ### PDF page count and multi-page navigation Currently only the first page thumbnail is shown. Could show page count and allow browsing pages in the lightbox. ## Inventory ### Inventory URL reflects selected entry Similar to how components now reflect `/components/:id` in the URL, inventory entries have no URL state — refreshing loses context. ## Grids ### Grid view layers Allow a storage grid to reference one or more separate "view layer" grids that share the same logical layout but use different source images. Example: the storage grid uses close-up photos of individual cells for identification, while a view layer uses a wider photo of the lid or top side for orientation. Key design points: - Grids get a classification: `storage` (can hold inventory) or `view` (display only, referenced by storage grids) - View layers may use a different panel sub-grid layout (fewer, larger panels) as long as the final logical row×col count matches the storage grid - In the grid viewer, layers can be toggled to switch between the storage view and any attached view layers - A storage grid can have multiple view layers (e.g. lid photo, tray photo, labeled overlay) ### Multi-cell grid storage selection A component stored in a grid should be able to span multiple cells, since larger parts often occupy more than one cell. The graphical cell picker in the inventory dialog should support selecting a range or set of cells rather than a single cell. The grid viewer should reflect multi-cell occupancy in its count badges and cell highlighting. ### Grid URL state Navigating into a grid viewer updates the URL correctly, but the grid list and draft state have no URL representation.