tools: a Tech Tools section, starting with codes on label stock

A place for the small utilities a technician reaches for at a bench. The
plugin owns no API and no tables: every tool runs entirely in the browser,
so an air-gapped site gets them for free and a bad network cannot break
them. Adding the next tool is a view, a route, and one entry in tools.js.

First tool is a barcode/QR generator. Content is typed text, a URL, or a
CSV (content,label,copies - quoted fields and an optional header both
handled), so a batch of a few hundred is one paste. Label stock is
adjustable in inches with five presets, and the code renders to an SVG
data URI rather than a PNG: a bitmap gets downscaled to label size and
smears the module edges a scanner reads, where SVG rasterizes at the
printer's resolution with hard edges.

It also carries the dot-grid rule that is easy to get wrong by eye. A
thermal head cannot render a fraction of a dot, so a code sized off the
grid gets uneven modules; pick a DPI and the page says what the current
size lands on and what to use instead. The quiet zone is blank label
rather than white baked into the code, so it can be tuned - and it applies
to CODE128 too, which needs clear space at each end and was letting bars
run into the caption.

Tech Tools is the first bundled plugin that owns no schema, which two
guards did not model: it belongs in the universal installer profile, and
upgrade-all reports it 'no-migrations' where every plugin was assumed to
report 'ok'. The migration test now asserts that status explicitly for
schema-less plugins, so a table-owning plugin whose chain went missing
still fails.
This commit is contained in:
cproudlock
2026-08-12 11:43:48 -04:00
parent 4d807ccb4b
commit f66763e6df
11 changed files with 1011 additions and 4 deletions

View File

@@ -71,6 +71,8 @@ EXPECTED_HEAD_REVISION['printedparts'] = 'printedparts0004txnrev'
# notifications indexes businessunitid, then adds the per-type grace window and
# the shared board category.
EXPECTED_HEAD_REVISION['notifications'] = 'notifications0005boardorder'
# warranty adds the proof-of-cover document columns on top of its anchor.
EXPECTED_HEAD_REVISION['warranty'] = 'warranty0002proof'
# Plugins built after the cutover: their 0001 baseline really creates tables the
# core chain never owned.
@@ -239,9 +241,21 @@ def test_upgrade_all_on_fresh_db_is_clean_and_idempotent(tmp_path, monkeypatch):
first = app.extensions['plugin_manager'].upgrade_all_plugins()
second = app.extensions['plugin_manager'].upgrade_all_plugins()
assert set(first) == set(PLUGIN_TABLE_OWNERS)
assert all(status == 'ok' for status in first.values()), first
assert all(status == 'ok' for status in second.values()), second
# A plugin that owns no tables (Tech Tools: every tool is
# client-side) carries no chain, so upgrade-all reports it
# 'no-migrations'. Assert that explicitly rather than letting it
# widen the 'ok' check, which would also swallow a table-owning
# plugin whose chain silently went missing.
schemaless = set(first) - set(PLUGIN_TABLE_OWNERS)
for name in schemaless:
assert first[name] == 'no-migrations', (name, first[name])
assert second[name] == 'no-migrations', (name, second[name])
migrated = {name: status for name, status in first.items()
if name in PLUGIN_TABLE_OWNERS}
assert set(migrated) == set(PLUGIN_TABLE_OWNERS)
assert all(status == 'ok' for status in migrated.values()), migrated
assert all(second[name] == 'ok' for name in PLUGIN_TABLE_OWNERS), second
insp = inspect(db.engine)
for plugin in PLUGIN_TABLE_OWNERS: