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:
65
README.md
65
README.md
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user