Skip to content
HN On Hacker News ↗

Born Twice: Specimen and Record at the Moment of Collection

▲ 5 points • 2 comments • by TimothyMJones • 1w ago • HN discussion ↗

Pangram verdict · v3.3

We believe this text is mainly AI, with some human-written content.

94 %

AI likelihood · overall

AI
1% human-written 99% AI-generated
SEGMENTS · HUMAN 0 of 1
SEGMENTS · AI 1 of 1
WORD COUNT 1,523
PEAK AI % 98% · §1
Analyzed
Oct 3
backend: pangram/v3.3
Segments scanned
1 windows
avg 1523 words each
Distribution
1 / 99%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,523 words · 1 segments analyzed

Human AI-generated
§1 AI · 98%

Line numbers refer to ironfist911.html as distributed (11,943 lines). Function names are as they appear in the source. A leading underscore marks a helper not meant to be called from markup. The section numbers (1.1, 5.11, and so on) are the file's own; its module map at line 1182 lists them, and searching the source for a number jumps to that section. B.1 Architecture File layout. One HTML document, no external requests. Lines 1 to 23 are document metadata (Dublin Core, citation, canonical DOI). Lines 24 to 26 are a JSON-LD block typing the file as SoftwareSourceCode, WebApplication, and ScholarlyArticle. Lines 27 to 634 are CSS, light and dark themes as CSS custom properties. Lines 636 to 1132 are markup: the three panes (field form, saved-record list, office tools) and the compiler drawer. Lines 1133 to 1173 hold the QR generator as its own plain script. Lines 1174 to 10706 are the main program, Parts 1 to 8. Lines 10707 to 10936 are the dialogs. Lines 10937 to 11689 are the compiler drawer (Part 9), a second script in its own closure. Lines 11691 to 11938 are the pane shell (Part 10), a third. Runtime layers. Four things carry the state; everything else reads through them. FIELD_DEFS (line 1312) is the schema. Each entry maps an internal key to a CSV/Darwin Core column name and a human label, with an optional type of boolean or number. Every exporter, importer, form reader, and the HTML table iterate this one array. _coerceFieldValue() (line 1461) is the only place typed values are converted on the way in from flat formats. Store (line 1599) is the persistence kernel: a single localStorage key (fieldVoucherRecords_v1, line 1573) holding an object of typed lists (voucher, addendum, uiState, list, bundle). Operations are all, get, put (upsert by id), remove, replaceAll, mutate, filter, lastWriteOk, and invalidateCache. Every write re-reads disk first, so two tabs on one workspace do not overwrite each other; mutate (line 1715) is the read-modify-write form. A refused write sets a flag and routes to the storage failure reporter. Upsert-by-id is the property the merge model rests on: two devices generating different UUIDs can union their exports without collision, and re-importing the same file is idempotent. Bus (line 1797) is a minimal publish/subscribe with on and emit. The list, gallery, problems note, and workspace bar redraw on vouchers:changed; addenda:changed and draft:restore work the same way. Listeners are wrapped so one failing listener cannot stop the rest. Draft (line 5074) is a plain object mirroring the form. Delegated input and change listeners update it as the user types; bulk writers (reset, restore, duplicate, GPS, EXIF) call _draftSyncFromDOM() (line 5076) immediately after setting values, because programmatic .value assignment fires no input event. voucherFromForm() (line 5384), the save gate, and the autosave all read Draft rather than the DOM. Workspaces. One URL parameter, ?ws=name (line 1296), turns the file into separate data sets. Every storage key gets the workspace name appended through _wsKey(); the default workspace keeps the bare keys, so data from earlier builds is where it always was. The list of known workspaces and the install's device tag are shared on purpose. Identifiers. Three UUIDs with three lifespans, all minted by uid() (line 1873, RFC 4122 v4 via crypto.randomUUID, falling back to getRandomValues, then to Math.random). occurrenceID is one per specimen, immutable, minted at draft start by voucherOccurrenceUUID() (line 2777), and is the foreign key for addenda and the dedupe key for merges. The Voucher ID (id) is the label and QR number; it is generated per draft but may be replaced by a preprinted sheet, and photos are re-keyed when it changes. sessionId() (line 2016) is one per browser install, stored under the legacy name deviceTag for backward compatibility, and stamped on every voucher and addendum at save. compileBatchId is one per compiler clear, stamped on compiled exports only. Storage keys. Records: fieldVoucherRecords_v1 (line 1573). Draft autosave: fieldVoucherDraftAutosave_v1 (line 5115). Pre-migration backup of URL-shaped IDs: fieldVoucherreport_redirect_backup_v1 (line 2896). Photos: IndexedDB database fieldVoucherPhotos_v1, store photos (lines 3299 to 3300), holding Blobs keyed by photo id with a voucherId index. All four take the workspace suffix. Shared across workspaces: fieldVoucherWorkspaces_v1 (line 1303) and fieldVoucherDeviceTag_v1 (line 2006). Legacy keys read once by migration and then ignored: fieldVoucherreport_v1 (line 1304), fieldVoucherCollectors_v1 (line 4159), fieldVoucherDwcExtraFields_v1 (line 4318). Record schema version constant: RECORD_SCHEMA_VERSION = "v2" (line 1579). Addenda model. Corrections are their own Store type (addendum), each with its own UUID, a parentOccurrenceID, a parentVoucherId, a type of georeference or redetermination, a payload, an author, a device tag, and a timestamp. The voucher row is never rewritten. computeCurrentStateFields() (line 2159) derives the current_* columns: the standing redetermination, and the standing georeference by supersession rather than array order (_geoHeads(), line 2259). Decoration happens at export and render time, never at write time. DERIVED_STATE_KEYS (line 2219) is computed from the same function so the two cannot drift. One door in. canonicalRecord() (line 2522) is the single entry for every saved and imported record: FIELD_DEFS keys plus four named extras, so an unrecognized column cannot ride into the store. addRecords() (line 2631) is the single import path for JSON, CSV, and take-home bundles. The compiler drawer uses the same canonicalRecord(). Export and import symmetry. JSON nests the addenda array. CSV, XLSX, and HTML serialize the same array into the addenda_json column. parseAddendaField() (line 2137) accepts either shape, so a flat round trip is chain-safe. Ways out. Files: CSV, JSON, XLSX, photo ZIP, HTML table, email, labels, QR sheets, and QR tags (Part 5). Take it home (section 5.5b, line 7131) writes one ZIP per part holding a viewer page, the JSON and CSV exports, the photos, a manifest with a SHA-256 for every file, and a README; a GitHub route cuts parts under GitHub's browser limits (line 7171). Import bundle (section 5.5c, line 7713) brings a bundle back and checks every photo against its manifest hash before reattaching it. Collection exports for Symbiota, Specify, and DiSSCo share one reader, _csPrepare() (line 8820). Init sequence (lines 10625 to 10705). Tracer install, kernel migration from legacy keys, theme, dates, kingdom, pristine snapshot, form listeners, Voucher ID and occurrenceID, Darwin Core fields, collectors, autofill guards, section state, autosave check, trip strip, timer arming listeners (click, input, change, capture phase), legacy URL-ID migration followed by vouchers:changed, store reconciliation and the snapshot offer, storage resilience check, the self test when asked for, session badge, top stamp, workspace bar, two-tab presence, and the associated-voucher readout. Third-party code. One item: Kazuhiko Arase's QR Code Generator for JavaScript, MIT, vendored at lines 1133 to 1173. It runs as its own script and sets window.QR; _ensureQR() (line 2747) only confirms it is there. Everything else, ZIP reader and writer, XLSX writer, EXIF reader and writer, JPEG re-encoder, SHA-256 through the browser's crypto.subtle, is in-file. B.2 Function reference All 488 functions in the file, in source order, grouped by the file's own sections. Each description is the one-line comment above the function in the source. 1.1 Workspaces (lines 1278 to 1306) _wsSanitize(v), line 1295. Folds any workspace name to lowercase letters, digits and hyphens, 24 characters max. _wsKey(k), line 1300. Storage key for this workspace; the default workspace keeps the bare key. _wsFileName(n), line 1302. Download filename for this workspace: pests_field_vouchers_2026-09-19.csv. 1.2 Field definitions (lines 1307 to 1526) _coerceFieldValue(def, value), line 1461. Turns a raw cell value into the type its FIELD_DEFS entry declares (boolean, number, text). _boolTrue(v), line 1481. True for true, "true", 1 or "1"; everything else is false. _mergeAddendumCopies(kept, incoming), line 1493. Two copies of the SAME addendum (same id) that disagree on isCurrentIdentification. 1.3 Kernel: Store and Bus (lines 1527 to 1756) _load(fresh), line 1621. Reads the whole record store from localStorage, using the cache when nothing has changed. _save(), line 1648. Writes the cache to localStorage and reports a refused write instead of swallowing it. all(type), line 1664. Every record of one type (a copy, safe to change). get(type, id), line 1666. One record by type and id, or null. put(type, id, data), line 1671. Adds or replaces one record, then saves. remove(type, id), line 1683. Removes one record by type and id, then saves. replaceAll(type, records), line 1704. Swaps out every record of one type in a single write. 801: this re-reads disk and then throws the re-read away. mutate(type, fn), line 1715. Read, modify, write against what is on disk NOW. fn receives the current list (a copy, safe to mutate) and returns the list to store; returning nothing keeps the copy it was handed. filter(pred, type), line 1726. Every record that passes a test, across one type or all of them. lastWriteOk(), line 1737. Did the last write to localStorage succeed? invalidateCache(), line 1743. Drops the parsed copy so the next read goes back to disk. 1.4 Storage failure reporting (lines 1757 to 1811) storageWriteFailed(), line 1768. True while localStorage is refusing writes. reportStorageWriteFailure(err, what), line 1770. Records a failed write and warns the user once. noteStorageWriteRecovered(), line 1789. Clears the storage warning once writes succeed again. on(evt, fn), line 1800. Subscribes a function to a named event. emit(evt), line 1802. Calls every function subscribed to an event; one failing