Skip to content

Releases: m-dohmen/openToolbox

v0.18.0 — Computed fields complete: memoisation, error containment, sum/avg(computed)

Choose a tag to compare

@m-dohmen m-dohmen released this 31 Aug 18:39
34d8a6d

What this release delivers

Completion of the computed field type first shipped in v0.14.0. The formula now runs once per record per render pass instead of on every render, a throwing formula is contained rather than silent, and computed fields stand in for stored number fields in sum/avg metric tiles. The release stage for the underlying OPEN-94 issue was missed when it closed (OPEN-46 pattern), so this release also catches up the change log against code that has been on main since v0.17.0 — the change set is purely additive and backwards-compatible.

Why this version

compute(record) ran on every render in v0.14.0: a 1000-record table with three computed fields paid 3000 function calls per render. v0.18.0 memoises the result on the record (held by a WeakMap, dropped with the record), caps error output to one console warning per unique (entity, field, record id, message) signature, and opens the closed count/sum/avg metric catalogue to computed sources whose formula returns a number. None of this changes a stored payload: computed values were never persisted, and the new behaviour only shows up at render time.

What users see

  • A computed field whose formula throws no longer blanks the table — it renders as a dash and warns once.
  • The portfolio demo's dashboard carries a new "Budget left" tile that aggregates the Budgetabweichung computed field with sum(variance) — visible proof that the closed metric catalogue accepts computed sources.
  • The five short READMEs (zh/es/fr/ja/pt) carry computed fields in their feature lists; the long README.md and README.de.md name the memo, the warning shape and the metric acceptance.
  • plugin/skills/opentoolbox-tool/SKILL.md carries the same computed-field paragraph as AGENTS.md so the skill stays a thin pointer to the canonical documentation.

Change log excerpt (full version in CHANGELOG.md)

Added

  • compute(record) runs once per record per render pass and is memoised on the record for the lifetime of the page (#58).
  • A throwing compute renders the field as a dash and writes exactly one console warning per unique combination of entity, field, record id and error message (#58).
  • A computed field whose compute returns a number stands in for a stored number field in the closed sum(field) / avg(field) metric catalog (#58).
  • AGENTS.md and CLAUDE.md gained a computed section spelling out the memo, the warning key and the metric acceptance; the diff between the two remains byte-identical (#58).
  • README.md and README.de.md rewrite the computed-field paragraph to name the memo, the warning shape and the metric acceptance (#58).
  • The five short READMEs (zh/es/fr/ja/pt) carry computed fields in their feature lists at the same depth as the English README (#61).
  • Portfolio demo's dashboard gains a "Budget left" tile that sums the Budgetabweichung computed field; eleven screenshots under docs/screenshots/ are regenerated to mirror the table with the new column and the dashboard with the new tile (#63).
  • The seven demo build prompts under docs/demos/portfolio/generating_prompt_*.md are refreshed so a reader in de/en/es/fr/ja/pt/zh meets the new metric at the same depth (#63).
  • plugin/skills/opentoolbox-tool/SKILL.md carries the same computed-field paragraph as AGENTS.md (#58).
  • test/smoke.mjs adds the memo, warning, metric-acceptance, sum/avg and 1000-record cases (#58).

Live demo

The published demos at https://m-dohmen.github.io/openToolbox/demos/ render the new behaviour:

Consciously not in this version

  • No computed-of-computed chains by reactive subscription. A computed field may read stored fields and other computed fields by name, but the memo invalidates on record identity, not on dependency tracking.
  • No persistent cache. Computed values are still never written to the data block; saving the file is byte-identical to the saved state of a schema without computed fields.
  • No new metric kind. The catalogue stays closed at count, sum, avg; sum/avg only opened up to computed-returning-number sources, which is the same gating the stored-field forms already had.

Upgrade notes

Nothing required. Schemas that already declared type: 'computed' in v0.14.0 inherit the memoisation and the warning behaviour transparently. Schemas that already used sum(field) / avg(field) in their metric catalog may now point at a computed field whose formula returns a number.

PRs in this release: #58 (code, docs, tests), #61 (short READMEs), #63 (demo + screenshots + prompts).

openToolbox v0.17.0

Choose a tag to compare

@lina-berger lina-berger released this 28 Aug 11:36
d7fa564

openToolbox v0.17.0

Before this release

Two recurring workflow gaps showed up against the same shape of data: a
Kanban-style view of an entity's records (consent state, project phase,
priority tier) and a consistent set of charts next to the dashboard tiles.
The framework only shipped a list view and a tile dashboard, so users who
wanted either had to keep a separate tool and copy data over. The single
existing chart was a CSS bar block plus a lone SVG ring — inconsistent
between each other and easy to break with a malformed declaration.

What 0.17.0 ships

A view.board per entity (#79, demo in #80, docs in #82).
Opt-in via the schema declaration view.board: { columnField, cardFields, limit }. Dragging a card between columns writes through the same mutate
path the form uses, so the move lands in Undo/Redo and the change log
with one entry. Keyboard covers the same path end-to-end (Left / Right /
Enter / Escape), touch goes through the native HTML5 drag-and-drop API
so an attached iPad keyboard behaves identically. Records that no longer
match any declared value land in an "Unassigned" reservoir at the right
edge of the board — they stay findable instead of being silently
re-categorised. Global search and field filters act on the board the
same way they act on the table, so the filter the recipient set up
keeps working when they switch tabs.

School-trip table
School-trip board
School-trip board next to table

Inline-SVG charts in the dashboard (#81, demo in #85).
The dashboard export now accepts a charts block alongside the existing
tiles: { type: 'chart', kind: 'bar' | 'donut' | 'line', ... }.
A new renderer in src/lib/charts.js replaces the CSS bars and the lone
SVG ring with one consistent code path. line aggregates per month
(count or sum(field)) and skips empty months instead of overdrawing.
validateChart surfaces malformed declarations as an objection tile in
the grid — no silent empty render. The renderer's sanitizeSvg contract
is enforced so no script, on* or external URL leaks into the output.
The print stylesheet switches to black-and-white hatching so the same
dashboard prints cleanly.

Portfolio dashboard charts

What changes for existing users

None, by design. v0.17.0 is a minor release and backwards-compatible
within the 0.17.x line. Domains that don't declare view.board or a
charts block behave identically to v0.16.1. The board, the dashboard
view, and the chart toggle only appear when the schema opts in, so an
existing payload opens the same way it did before. The renderer
replacement changes visual output for domains that already used the
old CSS bars — the new renderer matches the same intent but with
consistent geometry.

AGENTS.md and CLAUDE.md remain byte-identical, the same way they
have been since 0.16.0. CI's cmp check keeps matching.

Not included (consciously)

For the board:
WIP-limits, swimlanes, multi-select inside the board, and per-card
colours are out of scope at this version. The schema declares only
the four fields the board needs and no more.

For charts:
no drill-down, no per-series click action, no second axis, no
sparklines inside cards. A chart stays a tile — clicking it opens
the same drawer the rest of the dashboard opens, nothing more.

Where to start

  • Live demo: https://m-dohmen.github.io/openToolbox/demos/
  • Board walkthrough: docs/demos/school-trip/ (consent column,
    view.board.columnField: 'consent')
  • Charts walkthrough: docs/demos/portfolio/ (Dashboard tab,
    Projects by risk bar + Projects landing by month line)
  • Full change log: CHANGELOG.md
  • Wiki: Kanban-Board.md and Dashboard-Diagramme pages in
    openToolbox.wiki

Verification at the tag

  • npm ci — clean
  • npm run builddist/index.html 275.34 kB, gzip 86.56 kB
  • npm test — exit 0, all 11 suites (prompts-metrics,
    actions-delete-guard, timezone, domain-swap-crash, views, board,
    charts, smoke, multi-entity, demos, timezone-examples)
  • npm run build:demo — 42 prompts across 6 demos, no diff

PRs in this release: #79, #80, #81, #82, #83, #85.

v0.16.1 — Documentation patch: CHANGELOG links in the long and five short READMEs

Choose a tag to compare

@lina-berger lina-berger released this 27 Aug 20:16
dbb39a3

Documentation patch. Two additive changes that close a documentation gap
left by v0.16.0 — no behaviour change, no API drift, no compat impact.

Added

  • CHANGELOG.md linked from the version section of README.md and
    README.de.md, so a reader of either long README finds the project-wide
    change log and the GitHub release notes without leaving the document
    (#70).
  • Same paragraph added to the five short READMEs (zh, es, fr, ja,
    pt), so a reader in any shipped language lands on the same hint
    (#71).

Not included (consciously)

  • No content change in the short READMEs beyond the new paragraph; the
    Changelog topic was deliberately not introduced into them, only linked
    where their existing structure already mentions version history.
  • Wiki does not adopt the link — the Changelog topic does not exist there
    and there is nothing to point at.
  • AGENTS.md and CLAUDE.md untouched, so the byte-identity check in CI
    keeps matching the previous release.

Contained issues: OPEN-110, OPEN-112.

v0.16.0 — Read-only report copy

Choose a tag to compare

@lina-berger lina-berger released this 27 Aug 16:51
a25629c

v0.16.0 — Read-only report copy

Minor release: a new sidebar action, "Export a read-only copy", produces a second HTML file alongside the working one. The export carries settings.readOnly: true, a header banner with the carried version and the export timestamp, the same list and dashboard tables, and a change-log entry on the source so the right revision can be found later. Useful when a steering committee or a client needs the current stand without being handed edit rights.

What this fixes

Before this release, sharing the working file meant sharing the keys to it. The settings lock guarded the configuration; nothing guarded a forwarded file itself. A Berichtskopie (a read-only report copy) was the missing piece — a file the recipient can read, print and re-share but cannot save or change by accident:

<fileStem>-report-<YYYY-MM-DD>.html

Once generated, the export action keeps reaching the export sidebar entry; the rest of the sidebar stays where it always was. The export is a hand-out, not an edit path.

Decisions

  • Same build, different payload. Both source and export go through the same buildDocument(reportPayload). No second Vite entry, no second singlefile step — CI cannot drift the two apart, and the runtime stays closed at one entry point.
  • settings.readOnly: true on the copy, not a runtime check. Every write surface watches the flag and renders the read-only variant: Save is gone, undo/redo is gone, the Wizard, the AI dock, JSON/CSV import, Merge, bulk-select and the row-edit drawer are all hidden. Reference chips still resolve, but only by switching the entity — opening the referenced record would itself be a write surface.
  • Banner above the file bar. The banner is the visible signal. It carries the export label, the version from settings.version and the export timestamp, so a recipient sees the read-only status before they look for a Save button.
  • Filename pattern with the export date. <fileStem>-report-<YYYY-MM-DD>.html. A search for report- in the folder lands only on copies; the export never confuses with the source.
  • Change-log entry on the source. "Berichtskopie exportiert" (or "Read-only report copy exported" in English) is added to the in-memory audit log at click time and lands on disk with the next save. Without it, no later read can tell which report came from which revision.
  • No redact-before-share. The export copies the current file as-is; the read-only flag is the bound, and the recipient can still see every field their file already contained. If something must not be seen, the encryption is the right lever.
  • Ctrl+S is a no-op in the copy. Strg+S in the exported file does nothing; save is not only hidden but non-functional. The smoke suite asserts this (step 111).
  • i18n strings added in EN and DE. New keys cover sidebar.reportExport, report.banner*, toast.reportExported and log/report.exported in both languages. No new language, no copy-edit of existing strings.
  • SCHEMA.views and the saved-views machinery from v0.15.0 still apply. The export dialog does not capture the active view; the views dropdown and the start view behave the same in source and copy.

For existing users

  • A new sidebar action ships with the existing exchange group. "Export a read-only copy" sits next to the CSV/JSON import and the existing CSV/JSON export. The hand-out is one click away; no new tab, no new menu.
  • The copy carries settings.readOnly: true. The whole UI watches the flag — settings icon, New button, undo/redo, bulk-select and the edit drawer are all hidden in the copy. The recipient sees the data, prints it, re-shares it; they cannot save the file.
  • The header banner is the signal. The banner sits above the file bar and shows the export label, the carried version and the export timestamp. Recipients learn what they have from the visible chrome.
  • The source keeps an audit-log entry. "Berichtskopie exportiert" (English: "Read-only report copy exported") is added to the in-memory log at click time and is written to disk with the source's next save. The audit log answers "which report came from which revision" without the recipient needing to remember.
  • npm test runs the same nine suites; the smoke suite gained eight Berichtskopie steps (104–111). The steps cover button presence, filename pattern, change-log entry, payload write flags, banner text, absence of write surfaces, survival of CSV/JSON export, and Ctrl+S being a no-op in the copy. All green at the tag.

Consciously not included

  • No redact-before-share. The copy is the same content the working file carries; the read-only flag is not a redaction. Anything that must not leave the building belongs in the encryption, not the export.
  • No second Vite entry. Both source and export go through buildDocument(reportPayload) with the same single-file build. There is no separate bundle to drift.
  • No write-then-strip pipeline. settings.readOnly: true is on the copy before any layout is rendered; the export does not piggy-back on save.
  • No AI banner in the copy. ai.enabled stays false on the export, so the AI dock is hidden. There is no AI-visible signal that the file is a copy — the banner is the only signal, and that is enough.
  • No cross-file sharing of exports. The copy is generated from one source; there is no import/merge between two copies. Two exports of the same source differ only by export date.

The sidebar with the export action
The exported copy with its read-only banner

Wiki: Handing-Out-a-Read-Only-Copy
Full changelog: v0.15.0...v0.16.0

v0.15.0 — Saved views in the schema

Choose a tag to compare

@lina-berger lina-berger released this 27 Aug 13:52
87e6365

v0.15.0 — Saved views in the schema

Minor release: a new schema field, views, lets the schema author declare named combinations of query, filter and sort. Users pick one from a dropdown at the list head; the active view is mirrored to the normal UI state and persists in settings.views. A startView is applied once at mount.

What this fixes

Before this release, query, filter and sort were ephemeral: closing and reopening the file (or saving and reopening) reset them. Recurring views ("open items for my area", "overdue", "Q3") had to be re-entered by hand every session. The new SCHEMA.views block stores named combinations:

views: [
  { name: 'Open of mine', query: '', filters: { status: 'open' }, sort: { key: 'due', dir: 'asc' } },
  { name: 'Overdue',     query: '', filters: { status: 'open', due: { before: 'today' } }, sort: { key: 'due', dir: 'asc' } }
]

Users can rename, delete, capture the current view as a new one, or mark any view as the start view. All of this lives in the Settings editor (no new tab). The active view, captured views and start view all persist in the data block.

Decisions

  • Two persistence layers, intentionally separate: SCHEMA.views is the schema author's proposal (read-only after the schema is shipped). User-captured views live in settings.views so they survive schema updates and can be renamed, deleted, or marked as the start view. A future multi-entity migration moves schema proposals under ENTITIES[key].schema.views; the current format stays backwards compatible.
  • Merge: same name = theirs wins, disjoint lists union. applyMerge now accepts an optional { views: { mine, theirs } } and returns the merged list. Disjoint views are appended; renamed or modified views from either side replace by name. No view is silently dropped.
  • Start view applied once, not on every state update. A startApplied ref guards the apply: it runs at mount and after the user explicitly switches views, but a sort click does not re-snap the UI back to the start view. This is what makes "open with my start view" predictable without making the UI fight the user mid-session.
  • Settings editor, not a new tab. Rename inline, delete with confirm, toggle "open with this view", capture from the current state. Sits in the existing application block of Settings so the navigation surface stays flat.
  • i18n strings added in EN and DE as new keys (settings.views*, views.*, view.*). No new language, no copy-edit of existing strings.
  • Backwards compatible at every surface. Schemas without a views block behave identically to v0.14.x. The school trip demo gained three preset views (Alle, Zettel ausstehend, Geld offen) so the feature is visible without reading the docs.

For existing users

  • Schemas without a views block are unchanged. The dropdown at the list head only appears if the schema declares views, the schema proposes them, or the user has captured some. No schema migration is required.
  • Stored views are written to settings.views in the data block. Saving the file persists captured views and the start view. Reopening the file restores them. Nothing in settings.views is lost on save.
  • Merge now understands views. When two files are merged (applyMerge), view lists are reconciled by name. Renames and modifications from either side win; the merged list contains the union of disjoint entries. No view is silently dropped. The rule is documented in AGENTS.md and the new wiki page.
  • The start view is applied automatically when the file is loaded. No user action required; the start view behaves like the first paint of the list. Users who do not want a start view can clear it in the editor.
  • npm test now runs nine suites (the new views suite covers positive merge, negative conflict, applyView mirroring, and robustness against null/empty input).

Consciously not included

  • No cross-file sharing. Saved views live inside one file. There is no import/export path for views, no cloud sync, no "team views". This keeps the closed-file guarantee intact: a saved file is the database, and it carries everything it needs.
  • No multi-entity views today. view.entity is in the schema shape but not enforced; the current single-entity example is the supported path. The shape is forward-compatible with a future ENTITIES[key].schema.views migration.
  • No view-level permissions. Every view is visible to every user of the file. There is no per-view "private vs shared" toggle in this release.
  • No view-based automation. A view does not trigger emails, notifications, or background jobs. It is a named read filter, not an action.
  • No computed columns in views. A view can filter and sort by existing fields (including computed ones from v0.14.0), but it does not introduce new computed values; that boundary still belongs to the computed field type.

Saved views dropdown in the school trip demo

Wiki: https://github.com/m-dohmen/openToolbox/wiki/Saved-Views
Full changelog: v0.14.0...v0.15.0

v0.14.0 — Computed fields in the schema

Choose a tag to compare

@lina-berger lina-berger released this 27 Aug 07:34
02118ae

v0.14.0 — Computed fields in the schema

Minor release: a new opt-in schema field type, computed, lets a value be derived from other fields at render time instead of being maintained by hand and stored in the payload.

What this fixes

Before this release, derived quantities (budget variance = plan minus actual, days left, percentage complete, risk score) had to be kept as plain number fields. Users updated them by hand whenever one of the inputs changed, and they drifted the moment anyone forgot. The new type: 'computed' field replaces those values with a function the schema declares:

{ key: 'variance', label: 'Budget variance', type: 'computed',
  compute: (r) => r.plan - r.actual }

The result is rendered in the table, the read-only form field, the detail view, the CSV export, and any metric tile that aggregates it. Sorting and validation rules that reference the field work the same as for stored number fields.

Decisions

  • Why a function and not an expression string: the schema is already plain JS, the rest of the field definitions are functions (validate, transform), and a function gives field authors the full language for what they need (arithmetic, ternaries, calls into small pure helpers). An expression mini-language would have added parser surface area for a feature that does not need it.
  • Memoised per render pass, not globally: the value is recomputed when the schema author asks for a different one (e.g. after editing a value), and is cached for the lifetime of that render. A single user-visible change is one recomputation, not N. A 1,000-record smoke run stays well under the 500 ms wall-clock budget the issue asked for.
  • Errors never crash the app: if compute(record) throws, the cell renders as a dash () and the console receives exactly one warning per (singular, fieldName, recordId, errorMessage) tuple. A second failure of the same tuple in the same render is deduplicated — you get the signal without the spam.
  • validateMetrics now accepts sum(computed) and avg(computed) as well as the stored-field forms it already accepted. The catalogue stays closed: only count, sum, avg remain, and sum/avg are gated to numeric sources (which now includes computed returning a number). No new metric kind.
  • Backwards compatible at every surface: schemas that do not declare any computed field behave identically to v0.13.x. The portfolio demo gained a Budget left tile (sum(variance)) so the feature is visible without reading the docs.

For existing users

  • Stored number fields still behave as before. The change is additive: validateMetrics accepts one more source type, compute is a new key you may add to your schema, and the renderer handles the new field type. Existing schemas need no edits and produce the same output.
  • CSV export now contains the computed column when the field is in the visible columns. If your downstream tooling imports the CSV and expects only stored columns, you may want to hide the computed column from the export until downstream is ready.
  • Validation rules can reference a computed field the same way they reference a stored one. If a rule fires on a computed field whose formula threw, the rule sees undefined and the cell renders as ; the rule is not silently skipped.
  • The form renderer treats a stored 0 as 0 (previously it showed ). This is a tiny visual fix from OPEN-79 / PR #51 that v0.14.0 picks up; no value change in saved data.
  • npm test now runs eight suites locally (the new domain-swap-crash suite is part of the standard run since v0.13.1).

Consciously not included

  • No cross-record computations. A compute function receives one record at a time; totals across the record set belong to the metrics block, not to the field. This is the same boundary the closed metrics catalogue already enforces.
  • No computed-of-computed chains. A computed field can read stored fields and other computed fields, but only by name, not as a reactive subscription. If the underlying record changes between renders, the cache key (record identity) invalidates the memo.
  • No persistent cache. Computed values are never written to the data block. Saving the file is byte-identical to the saved state of a schema without computed fields. This is the property that makes the feature safe to add to long-lived schemas.
  • No import path. A computed field cannot be supplied through CSV/JSON import; the importer rejects unknown keys and ignores computed keys in payloads. Imported records compute the value on the next render, like locally edited records.
  • No AI write path. The AI integration can read computed values (so it can answer questions like "what is the total budget variance?") but cannot write to a computed field; the field is read-only in the form and absent from the AI's allowed-keys list.

Dashboard with the new "Budget left" computed tile

Wiki: https://github.com/m-dohmen/openToolbox/wiki/Computed-Felder
Full changelog: v0.13.1...v0.14.0

v0.13.1

Choose a tag to compare

@lina-berger lina-berger released this 24 Aug 16:44
91470c2

Patch release: two post-release fixes since v0.13.0, no new features.

Fixed

Demos gallery restored on GitHub Pages (#56, OPEN-89) — The pages.yml rewrite in OPEN-78 had dropped the copy step that brought demos into the deployment artifact, so every published page went live without the demos gallery. docs/demos, the legacy /demo/ path and logo.svg are copied into the artifact again. New: before upload, the workflow now hard-verifies the artifact contents (dist/demos/index.html, an example domain, dist/demo/index.html, logo.svg) and fails the build job naming the missing file instead of silently publishing an incomplete site.

Crash-safe fixture restore in the domain exchange (#50, OPEN-81) — A smoke-test run killed with a hard signal (SIGKILL) left the fixture mutated inside src/domain.js, and the next run then read the mutation as the "original". Three layers instead of one: the pristine file is anchored outside the repo (tmpdir, hashed per checkout) and restored by the next run before it reads anything; SIGINT/SIGTERM/SIGHUP and process.exit() restore the original while dying, then re-raise; the lock directory is gitignored so a killed run leaves no untracked residue. Covered by a new suite, test/domain-swap-crash.mjs, which drives both death modes in child processes and measures against the pre-victim baseline.

Decisions

  • SemVer patch, both fixes together: one touches test infrastructure only, the other CI/artifact handling — no product behavior, no schema or single-file format change. Neither justified its own release; both were already merged to main.
  • Hard artifact verification added rather than trusting the copy step: this regression shipped green because nothing checked what was actually uploaded. The check fails loudly with the missing filename so the same class of regression cannot publish silently again.

For existing users

Nothing changes in product behavior, data format, or the save-as-single-HTML flow. If you run the test suite locally: it now counts eight suites (the new domain-swap-crash suite), and interrupted runs clean up after themselves even when killed hard.

Consciously not included

  • The same signal-safe pattern is not yet applied to scripts/build-demo.mjs (same hand-work window, documented as accepted residual risk in the module header).
  • The process track around gate sharpening (OPEN-69) remains open pending a decision outside this release.

Full changelog: v0.13.0...v0.13.1

v0.13.0 — Multi-select with bulk actions, record duplication, metric tiles

Choose a tag to compare

@lina-berger lina-berger released this 24 Aug 12:29
9e55be4

Lists treated every row alike: changing one field across many records meant opening each record in turn, there was no way to copy an existing record as a starting point, and the dashboard showed widgets but no numbers. This release adds three user-facing capabilities to the single-file app and tightens several paths that could quietly let bad data or network references through.

Multi-select with bulk actions (OPEN-19)

  • Checkbox per row plus a header checkbox for the visible page (indeterminate state on partial selection), shift-click for range selection, and an "N selected" counter. Selection is deliberately limited to what is visible — bulk changes never reach filtered-out records.
  • The action bar offers set enum field value (only when the entity has an enum field) and delete selected with confirmation; from 50 records the count must be typed. Records protected by references are named and kept — the same protection as in the form.
  • Bulk actions run through the same applyActions pipeline as model suggestions: schema rules still apply, records already carrying the target value are skipped, and one pass is exactly one audit-log entry — one Ctrl+Z restores everything.
  • Fixed en route: exportCsv wrote resolved reference titles back into the live data block, replacing IDs without an audit entry. It now works on a copy.

Bulk selection

Per-record duplication (OPEN-20)

  • A copy button in a new action column (row hover/focus) and next to Delete in the form footer. The copy carries the saved values of the original under a new ID with a localized "(Copy)" title suffix; its form opens for editing right away.
  • Duplication goes through the normal change path: undo stack (Ctrl+Z removes the copy completely), amber unsaved marking, audit log derives a Create event on next save — nothing touches the file before you save. The attachment budget is enforced before the copy exists.

Metric tiles in the dashboard (OPEN-21)

  • An entity can now declare metrics in the domain; the dashboard renders tiles above the existing widgets: count, sum over a numeric field and avg fixed at two decimals, each optionally with a filter criterion. Clicking a tile jumps to that entity's list.
  • Purely declarative closed catalog — no eval, no second validation site, computed locally at render time, nothing written into the embedded data block. Invalid declarations (unknown op, sum over a non-numeric field) are rejected by name in their own tile instead of silently ignored. Without a metrics declaration the dashboard is exactly as before.

Dashboard with metric tiles

Behavior changes worth knowing

  • Wizard CSV and JSON imports are now validated (OPEN-74). Both paths previously bypassed the schema check shared by form and CSV import. Every incoming record runs through coercion + validation; rejected rows appear by name with line number and rule text. JSON import is atomic — one violation rejects the whole file, unknown keys are rebuilt away rather than merged unchecked. Files that used to slip through may now be refused, deliberately.
  • The SVG sanitizer strips style attributes entirely and removes <style> elements (OPEN-71). Both could smuggle external CSS or @import url(...) into the output file, breaking the no-runtime-network guarantee. Inline coloring via style is lost (documented trade-off); presentation attributes (fill, stroke, stop-color) and internal gradient references keep working. Removed root attributes are reported instead of dropped silently.
  • Due dates use the local calendar instead of UTC (OPEN-73; also applied to all examples/*.domain.js in OPEN-83). Seed offsets, overdue highlighting and days-left were shifted by up to ~12 h depending on timezone; they now key on the local day.
  • Number cells show a stored 0 as 0, not the empty placeholder (OPEN-79).
  • The AI-suggestion deletion path respects reference protection like every other delete (OPEN-70).

Tooling & docs

  • CI gates hardened: closed-file verification of dist/index.html, Pages deployment only after a green build (OPEN-78); smoke suite binds its mock port dynamically so parallel runs don't collide (OPEN-75); drawer-focus flake fixed (OPEN-43); build-prompts emits metric tiles as a prompt section (OPEN-44); PPWR seed record V-103 brought into compliance (OPEN-65).
  • Docs: mass editing and metric tiles documented across all seven READMEs, screenshots refreshed, inventory numbers corrected against the code — including the i18n key count, verified by parsing src/i18n.js: 474 keys, en/de complete pairs.

Consciously not included

  • Bulk editing of free-text fields, moving records between entities, selection reaching beyond visible/filtered hits.
  • Pre-filtering when clicking a metric tile — the jump opens the unfiltered list.
  • Multi-record duplication, template catalogs, duplication across entities.

Full changelog: v0.12.0...v0.13.0

v0.12.0 – Global search, field filters & sortable columns

Choose a tag to compare

@lina-berger lina-berger released this 23 Aug 20:42
ebba67f

Lists used to appear only in the order records sit in the data block, and there was no way to look anything up across a file: finding the largest value, the nearest deadline, or one record among hundreds meant scanning by eye. This release adds two standard table-tool capabilities to the single-file app, implemented entirely client-side over the embedded data block.

Global search and field filters (OPEN-17)

  • The header now has a search box that runs a live, case-insensitive full-text search across all fields of all entities — including record IDs, attachment filenames (never base64 payloads), resolved titles of reference fields, and calculated fields. Match counts per entity show on the tabs, matches are highlighted with <mark>.
  • Per-entity field filters in a sidebar, typed by field type: text contains, choice multi-select, number range, date range. Facet fields keep their quick-filter chips with counts. Active filters appear as removable chips above the table, with "clear all" from two chips up.
  • Filters combine with AND between fields, OR within a multi-select. Empty number fields don't fall through range checks (Number("") === 0 trap covered).
  • Entities whose schema has no filterable types simply get no filter UI but stay searchable.

Global search

Sortable columns (OPEN-18)

  • Click a column header to sort ascending, click again for descending, click a third time to return to the original data-block order. Arrow indicator and aria-sort reflect the active state.
  • Comparison is type-aware: numbers numerically (10 sorts after 9, not as text), dates chronologically, text and enums with locale-aware localeCompare in the current UI language. Reference columns sort by the resolved title of the target record, not the raw ID; attachments by filename.
  • Empty values group consistently at the bottom in both directions; ties break over the data-block order, so sorting never flickers.

Decisions worth knowing

  • Search, filter and sort state lives only in component/session state. Nothing touches localStorage, IndexedDB, or the embedded data block — under file:// those stores are unreliable, and Save continues to write a new HTML file with the data block as the single source of truth. Reload intentionally resets view state.
  • Sorting logic lives in src/lib/sort.js, search in src/lib/search.js; both are pure-function modules covered by new assertions in all three test suites.

Behavior changes for existing files

  • Every entity list now opens sorted ascending by its first column instead of raw data-block order. Clicking the active header twice more returns you to the original order for the session.
  • schema.search is no longer read. The old per-entity list-search configuration is superseded by the global header search, which needs no schema configuration. Existing entries are ignored harmlessly, but remove them when you next touch the domain.

Consciously not included

  • Multi-level sorting (primary + secondary key chosen by the user), persisting sort preference beyond the session, and sorting inside dashboard widgets.
  • The remaining feature requests from the August ideas round are still in development and will ship in a later release: per-record duplication (OPEN-20), multi-select with bulk actions (OPEN-19) and metric tiles for the dashboard (OPEN-21).

Full changelog: v0.11.0...v0.12.0

v0.11.0 — Undo and redo for the session

Choose a tag to compare

@lina-berger lina-berger released this 17 Aug 12:12
bb9d8d6

Undo and redo, for the session

A misclick — a wrong edit, a deleted record — used to be recoverable only by
"save as", closing without saving and reopening the last saved copy, and
manually copying the old values back in by hand. Since the file is the
database, there was no history to fall back on inside a session.

The decision

Every create, edit and delete now goes onto an in-memory undo stack —
Ctrl/Cmd+Z undoes it, Ctrl/Cmd+Y (Ctrl/Cmd+Shift+Z also works) redoes it, and
the same two actions sit as buttons in the file bar for anyone who prefers a
click. This covers every path that changes data: the record form, AI-proposed
changes, CSV import, JSON import, the wizard, and merging a copy that came
back.

Two things were deliberately decided this way rather than the more ambitious
alternative:

  • The history lives in the tab's memory only, capped at 50 steps. It is
    never written into the embedded data block — a version history that
    travels with the file would be a second, competing source of truth, and
    this template's one rule is that the file's payload is the only truth.
    Saving does not clear the stack and does not persist it either; closing
    the tab or reopening the file loses it, same as any unsaved state.
  • A new change clears the redo stack, like any editor — going back three
    steps and then typing something new makes the three you undid unreachable,
    rather than leaving a branching history nobody asked for.

Storage cost is a stack of object references into the record set, not deep
copies — arrays are rebuilt on each change but untouched records stay shared
by reference, so the overhead for 50 steps is a few KB for ordinary fields.
The one case worth knowing: repeatedly editing an attachment field on the
same record keeps up to 50 different attachment byte-strings alive at once
until the stack rolls over, bounded by the existing attachment budget (4 MB
per file). Negligible for text, enum and number fields, which is the common
case.

What changes for existing tools

Nothing to configure — the two buttons and the shortcuts are always there.
Typing in a text field still gets the browser's own field-level undo; the
shortcut only takes over once focus moves outside a text input, so the two
never fight over the same keystroke.

What's deliberately not in this release

No undo for settings changes (theme, branding, AI configuration) — scope was
records and entities only. No visual toast on undo/redo — the button's
enabled/disabled state already signals whether there's anything to step
through, and a toast on every keystroke felt like noise rather than
feedback. No persistent version history across saves or sessions — that is
a different feature with a different set of trade-offs, not a bigger version
of this one.