9.9 KiB
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:
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:
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 usec.namedirectly - Server-side: PDF conflict checks, sanitize calls, and rename logic are inline in
route handlers — could be extracted into a
pdf_servicehelper - 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 <table> 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
tolerancecould parse20as{ negative: 20, positive: 20 }and-10/+30as{ 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 > 200Kmatching 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
todofield" - "all SMD resistors with no datasheet attached"
- "all components missing a
packagefield"
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) orview(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.