Update README and future-plans to reflect current state

README: add PDF attachments, maintenance menu, mv-sync build step,
resizable pane, URL-based navigation, word-split search, grid highlights.
future-plans: add render_field_value integrations, field types, PDF paging,
inventory/grid URL state; update state variable list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 02:54:14 +00:00
parent 58c93f2bd0
commit 13ab5867c7
2 changed files with 99 additions and 25 deletions

View File

@@ -9,24 +9,49 @@ field values, physical storage locations, and visual panel/grid layouts.
Define reusable component types (e.g. Resistor, Capacitor, IC) with custom fields per component. Define reusable component types (e.g. Resistor, Capacitor, IC) with custom fields per component.
Components show up in inventory entries and can be navigated to directly from storage locations. Components show up in inventory entries and can be navigated to directly from storage locations.
- Word-split search: searching `0603 res` matches `Resistor 0603`
- Selected component reflected in URL (`/components/:id`) — survives page refresh
- Resizable list pane (width persisted in localStorage)
- Duplicate button to quickly clone a component
- Field values sorted alphabetically, rendered centrally (units, URLs as links, extensible)
### Fields ### Fields
Create custom field definitions (e.g. `resistance`, `capacitance`, `package`) that can be attached Create custom field definitions (e.g. `resistance`, `capacitance`, `package`) with an optional
to any component. Field values are entered per-component instance. unit suffix. Unit is appended directly to the value with no space, so you can write `4k7` for a
resistance field with unit `Ω` and it displays as `4k7Ω`.
URL-like field values (beginning with `http://` or `https://`) are automatically rendered as
clickable links.
### Inventory ### Inventory
Log where things are stored. Each inventory entry links a component to a storage location. Log where things are stored. Each inventory entry links a component to a storage location.
Supported location types: Supported location types:
- **Grid cell** — a specific row/column in a named grid (e.g. drawer divider box) - **Grid cell** — a specific row/column in a named grid (e.g. drawer divider box), picked
visually with a graphical cell picker
- *(plain entries without a grid reference work too)* - *(plain entries without a grid reference work too)*
Notes on inventory entries are per storage location (not per component).
### Grids ### Grids
Model physical storage grids (drawer organizers, parts boxes, etc.). Model physical storage grids (drawer organizers, parts boxes, etc.).
1. Upload a photo of the grid 1. Upload a photo of the grid
2. Set up corner points to map the image to a logical grid 2. Set up corner points to map the image to a logical grid (corners can extend outside image bounds)
3. Define row/column counts 3. Define row/column counts
4. Click any cell to see what's stored there 4. Click any cell to see what's stored there — component entries are links, middle-click opens in new tab
5. Each cell shows a green count badge of how many components reference it
6. Navigating to a grid from a component detail highlights and scrolls to the relevant cell
### PDF Attachments
Attach PDF datasheets or other documents to components.
- PDFs are stored with a sanitized human-readable filename derived from the display name
- Rename a PDF and the file on disk is also renamed, atomically (uses `renameat2 RENAME_NOREPLACE`
via `tools/mv-sync`)
- First-page thumbnails generated automatically via `pdftoppm` (poppler-utils) if available
- Multiple components can share the same PDF
- Click any thumbnail to open it full-size in a lightbox
### Templates (Name Formatters) ### Templates (Name Formatters)
Write JavaScript formatter functions that generate smart display names for components based on their Write JavaScript formatter functions that generate smart display names for components based on their
@@ -48,10 +73,18 @@ Multiple formatters are tried in order.
The template editor includes a test data box so you can preview the output without needing real The template editor includes a test data box so you can preview the output without needing real
inventory data. inventory data.
### Maintenance
A ⚙ menu in the top-right corner provides maintenance operations:
- **Generate missing PDF thumbnails** — scans all PDFs and generates thumbnails for any that
don't have one yet (useful if `pdftoppm` was unavailable at upload time)
## Requirements ## Requirements
- Node.js >= 25 - Node.js >= 25
- npm - npm
- `gcc` (to build `tools/mv-sync` — only needed once)
- `pdftoppm` from poppler-utils (optional, for PDF thumbnails)
## Install ## Install
@@ -69,6 +102,15 @@ cd electronics-inventory
npm install npm install
``` ```
## Build native tools
```bash
cd tools && make
```
This compiles `mv-sync`, a small helper that performs an atomic rename-without-overwrite using
`renameat2(RENAME_NOREPLACE)` (Linux 3.15+). It is required for PDF file operations.
## Run ## Run
```bash ```bash
@@ -94,8 +136,9 @@ PORT=8080 BIND_ADDRESS=0.0.0.0 npm start # both
All data is stored locally in a `data/` directory created automatically on first run: All data is stored locally in a `data/` directory created automatically on first run:
- `data/db.json` — component, field, inventory, grid, and template records (flat key-value store) - `data/db.json` — component, field, inventory, grid, template, and PDF records (flat KV store)
- `data/uploads/` — source images uploaded for grid setup - `data/images/` uploaded source images and component/inventory photos
- `data/pdfs/` — uploaded PDF files and their thumbnails
No external database is required. No external database is required.
@@ -107,11 +150,17 @@ lib/
storage.mjs Server-side KV store wrappers storage.mjs Server-side KV store wrappers
kv-store.mjs JSON file-backed key-value store kv-store.mjs JSON file-backed key-value store
ids.mjs ID generation ids.mjs ID generation
grid-image.mjs Grid image processing helpers
tools/
mv-sync.c Atomic rename helper (renameat2 RENAME_NOREPLACE)
Makefile
public/ public/
app.mjs Single-page app (vanilla JS ES modules) app.mjs Single-page app (vanilla JS ES modules)
templates.html HTML templates (lazy-loaded) templates.html HTML templates (lazy-loaded)
style.css Styles style.css Styles
lib/api.mjs Fetch wrappers for the REST API lib/
api.mjs Fetch wrappers for the REST API
dom.mjs DOM helpers
views/ views/
grid-setup.mjs Canvas-based grid corner editor grid-setup.mjs Canvas-based grid corner editor
``` ```

View File

@@ -1,26 +1,25 @@
# Future Plans # Future Plans
## Big refactor: app architecture ## App architecture
### parse_url mutates too many module-level variables ### parse_url mutates too many module-level variables
`parse_url()` directly assigns to a large number of module-level state 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`, (`section`, `grid_view_state`, `grid_tab`, `current_grid_id`, `grid_draft`,
`current_panel_idx`, `grid_source_id`). This is fragile and hard to reason about. `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, 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. and have `parse_url()` return a new state value rather than mutating globals:
Something like:
```js ```js
function parse_url(path) { function parse_url(path) {
// returns a state object, touches nothing external
return { section, grid_view_state, current_grid_id, ... }; return { section, grid_view_state, current_grid_id, ... };
} }
state = parse_url(location.pathname); render(state);
``` ```
Then the caller assigns it: `state = parse_url(location.pathname); render(state);`
### render() if/else chain ### render() if/else chain
The render dispatcher violates stated preferences — long chains of bare `else if` The render dispatcher is a long chain of bare `else if` branches. Replace with a
branches. Replace with a lookup table of arrow functions: lookup table:
```js ```js
const SECTION_RENDERERS = { const SECTION_RENDERERS = {
components: render_components, components: render_components,
@@ -29,14 +28,40 @@ const SECTION_RENDERERS = {
grids: render_grids, grids: render_grids,
templates: render_templates, templates: render_templates,
}; };
function render() { sync_nav(); SECTION_RENDERERS[section]?.(); }
function render() {
sync_nav();
SECTION_RENDERERS[section]?.();
}
``` ```
### General module structure ### app.mjs monolith
As the app grows, `app.mjs` is becoming a monolith. Consider splitting into `app.mjs` is large. Consider splitting into per-section modules
per-section modules (e.g. `views/components.mjs`, `views/grids.mjs`) that each (`views/components.mjs`, `views/grids.mjs`, etc.) that each export their render
export their render function and own their local state. function and own their local state.
## Field system
### Field rendering integrations
`render_field_value()` in `app.mjs` is the central place for field display logic.
Planned extensions:
- Mouser/Digi-Key part number fields → auto-craft links to product pages
- More URL-like patterns (without `https://` prefix)
### Field types
Currently all field values are free-text strings. Could benefit from typed fields
(numeric, enum/dropdown) for better formatting and validation.
## PDF / files
### 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 URL state
Navigating into a grid viewer updates the URL correctly, but the grid list and
draft state have no URL representation.