Changelog
0.10.0, 2026-10-15
Upload a spreadsheet and refiroo proposes a model and an import. Adjust both while you watch the effect on your rows, and load the next version of the file the same way.
Releases are numbered by plan, not by date, so a higher number can ship before a lower one. All releases
Full changelog
Released 2026-10-15. This release was not tagged in the repository. This is the changelog's own text for the release; its links to files point at those files at the tag.
Base 0.10 is released: the model designer and spreadsheet onboarding, accepted by R (a full review
of every change since 0.9.0, the full suites, the 49 browser runners, the accessibility and visual
gates with the spreadsheet and cell surfaces, the schema 0.10 checks and the budgets). Schema 0.10
(an application's navigation) is a released schema language; the starter stays content release
0.9.5 on schema 0.9.
Six migrations (20261012_01, source files and the profile and dryRun job kinds;
20261013_01, a model draft's import profile; 20261014_01, a draft's proposals and the
inference job kind; 20261015_01, the profileCheck job kind; 20261016_01, a source file's
column statistics per block; 20261017_01, indexes on the jobs a draft, a profile or a source file
has); a database of 0.10.0-alpha.1 upgrades on start.
Fixed (0.10 R)
- An import that deletes the records missing from its file (
missing: delete) through a source keyed by name no longer deletes a record a refused row names (an ambiguous name) (R-F1). - The dry run's difference from the last one compares each source with its own figures when several load one type from one sheet (a list read block by block, translations by language) (R-F2).
allowRelationwithrequiredto a released type is refused with its impact, as a required attribute is (R-F3).- A list's single-valued relationship cell finds its relationship among every page of the record's relationships (R-F4); a relation column's chips use the cell's width again beside the edit icons (R-F18).
- A file's profile no longer blocks its owner's next upload and the draft's steps; a file's delete
stops every job reading it first (R-F5, R-F8); inference and the profile's statistics renew their
lease and stop for a cancel request (R-F6, R-F7); a dry run or check whose file went fails
SOURCE_FILE_NOT_FOUND; the sweep supersedes a file a concurrent upload left current (R-F9); applying proposals locks the draft first (R-F10); a profile-only step's audit entry names the profile (R-F11). - A rename or another step that cannot change the load leaves the running dry run to finish rather than stopping it with none to follow (R-F15).
- Checking the next version of a workbook that loads related records (translations) took 56 s at 200 rows: the related records are found through each row's own relationships (R-F16). At 10,000 rows a dry run's or a check's recording joined its fresh outcomes row against row: outcomes are inserted with their source and row, and the summary analyzes the job tables first; W1′'s check at 10,000 rows 16 s again (R-F19).
- The dry run's, the check's and the inference's lookups of their earlier jobs use an index
(migration
20261017_01) (R-F14). - Import profiles: an earlier read of the check answering late no longer sets the page back to Checking (R-F17).
- History names a renamed element by its name before the step; an editor table's cells break between words; Export, Import and both Load dialogs choose their workspace with the shared field, every page read; the editor's drawer counts every step of the draft.
Added
-
Base 0.10 A8c: lists of one shape read as one list under their headers (D140–D146; J34.10, J27.8). Three lists or more of a sheet laid out for reading under one title (or none) whose columns have the same kinds carry, on each list's proposal, the alternative "Read these N lists as one list under their headers": one step replacing them by a parent list of the headers and one list contained in it, a source per former list fixed to its header. A row under a list's header answering none of its yes/no columns is a second header row (a note with
meaning: "heading"), skipped. Import: a source'sparent(objectTypeKey,matchBy,value,missing) sets every row's parent,PARENT_BOUND_TWICEbeside a parent column; a source keyed by name finds a row's record among its parent's children, one key under one parent across sources.addSourceandsetSourcetakeparent.test:browser:authoring-a8gains the alternative applied from a list. -
Base 0.10 A9: a list's cells edited in place (D131–D139; J02.7, J13.11). In a collection or a record's table, the cell under the pointer shows a pencil and, where an optional value or a single relationship is set, a trash beside its value (always on touch); a click elsewhere still opens the record. A value or name is edited in the cell; a relationship a record holds one of is set, changed (among the binding's candidates) or deleted; one it holds several of is edited in a dialog of ticked records, saved as one inline atomic batch of the records added and removed. A toast says whether a save went to Live or the open change set. Ancestor columns, containment, required ownership, read-only bindings, computed fields and publications show no icons. The details panel's remove and the relationship panel's delete are one dialog each, shared with the cells. No API change.
test:browser:authoring-a9. -
Base 0.10 A8b: the editor's source pane list by list (D122–D130; J34.10). A sheet laid out for reading shows its lists under their titles, each Proposed, Loaded or Not loaded, the open list's columns under it (one tree, keyboard operable); a selected list shows its titles, cells, note, what it loads, Proposed because and the sheet's cells around it. Adjust list… sets its header row, last row and columns over the cells (one
setSource, and Name the list after the new header); Don't load this list removes its source and, when nothing else needs it, its list type, in one step; Load as a list… reads a rectangle as a list. API:GET /source-files/{id}/profile/sheets/{sheet}/cells?range=(paged rows of staged cells),POST /model-drafts/{id}/inference/list(a rectangle'slistBlockproposal, not stored),422 INVALID_RANGE;setSourcemoving a header row or narrowing a block keeps each binding on its column letter, the rest unmapped.test:browser:authoring-a8. -
Base 0.10 A8a: lists of a sheet laid out for reading, through the API (D114–D121; J34.10). A sheet holding no table but lists side by side and under each other is read list by list: each column run split at empty rows, prose left out, each list's header, the titles above it and a note under its header (
multiple answers,multiple choice,free field, …); the profile's sheets gainlaidOut, its blockstitlesandnote, and column statistics are kept per block (migration20261016_01; a column holding only its header listed withfilled0; an overlap names itsblock). The inference proposes alistBlockper list: a list type loading its block by name, its translations (else "Language k"), a yes/no column as an attribute, two or more as a flag set, the note row skipped and the note the type's description; a navigation group per title over two lists or more; a free field or a header without values is a warning. A source's key may be{"field": "name"}(AMBIGUOUS_NAME_KEY,KEY_NOT_BOUND);addSourceandsetSourcetake it. W3 gains a second band of lists with a workbook's shapes. -
Base 0.10 A7: model-description sheets and matching across files (D106–D113; J34.9). A sheet of a field, a type and a target column is read before every other rule: the described type, a list per target read from its sheet by its ID and name (a placeholder ID skipped), a list to be defined added with nothing to load, a relation per lookup field, an attribute per plain field, one set per flag target (
modelDescription,describedList,describedField,describedFlags). Extend mode across files: a sheet matched to a type by its fields, a proposed list to an existing type of its name, a code column to a stored unique attribute; missing values and name drift against the stored records in the evidence and the warnings, each with an alternative (create on load; load the file's names). A source'srowsWithoutKey(refuse,skip,SKIP_NEEDS_KEY) andskippedRowsin the binding report and the dry run;setSourcetakes it. The placeholder words gaintbc,to be defined,to be decided,to be confirmed. The editor: a description sheet's columns read Describes the model, the dry run's rows skipped figure, the new evidence words;test:browser:authoring-a7. W2's generator returns its structures. -
Base 0.10 A5c: the next version of a workbook (D82–D85, D104, D105; J35). A file uploaded to a stored import profile is profiled and checked by a job of kind
profileCheck, writing nothing: columns new, missing and renamed (same position, at least half the values shared), the profile against the active release, the dry run of the file through the profile as it would read it, new values of match columns, name drift and records missing from the file (GET /import-profiles/{key}/check, itsnew-values,drift,missing-recordsandrows;POST …/checkchecks again). A source'smissing(report,delete): records no row names are counted by every binding and the dry run, and deleted only into a change set, each with its own relationships (422 MISSING_DELETE_NEEDS_CHANGE_SETlive); the kindsetMissing. Settings › Data operations › Import profiles lists the profiles; a profile's page uploads the next version, shows the check (Accept a rename, Ignore a new column, keep or delete missing records) and loads into a change set whose review holds only the differences. The editor's Load dialog and the profile's share their parts (ChangeSetLoad).test:browser:authoring-a5's A5c walk. Revised the same day after the user's run on the development server: Import profiles is a catalogue beside the selected profile, as Workspaces and Webhooks; a profile's answer carrieslastLoad(the latest import naming it and whether its change set is open, published or discarded), shown in the catalogue and on the profile's page, where a profile loaded from the editor read "no file uploaded"; the record page's breadcrumbs becameui/Breadcrumbs, which a job's page uses too (Jobs › the job). A settings group Data operations holds Import profiles (at/settings/import-profiles; the old/settings/importsstill opens it) and Jobs (renamed from Jobs and feed; the change feed's retention moved to Consumer APIs), ahead of Integration. -
Base 0.10 A6b: the editor filled by the inference (D94; J34.4, J34.6, J34.8). New from a workbook asks what to propose (a new application with its name, additions to the model, or nothing) and the workspace the columns are compared with; once the workbook is profiled the editor proposes and applies the proposals, one step each, and shows the sheet's type. What is still as proposed reads Proposed in the model and source panes (a tone of its own) and its centre says Proposed because, with its evidence and its alternatives as steps; History names each proposal; ⋯ › Propose again…; disagreements are Issues with Go to column. Map to… reads a column as a related record matched by a unique attribute, or as a value of a related record under its parent; a column's centre lists its most frequent values.
test:browser:authoring-a6. -
Base 0.10 A6a: inference over the table patterns (D86–D98; J34.4–J34.6), through the API.
POST /model-drafts/{id}/inferenceruns a job of kindinferenceover the draft's source file in new-application or extend mode;GET …/inferencereads its counts, disagreements, warnings and the elements still as proposed;GET …/inference/proposalslists each proposal with its pattern, columns, one-sentence summary, evidence, operations, alternatives and requirements; andPOST …/inference/applicationwrites the open proposals into the draft, one history step each, with one dry run after the last. The patterns: a sheet's key and name, a repeated parent with the columns constant within it, level columns, a code and name pair with its name drift, flag columns with a multi-valued column beside them, multi-valued cells, text lookups with their placeholders, references to another sheet, translations by row and by column, GUID columns, every other column as an attribute, and a navigation group of a sheet's lists (raising the schema language to 0.10 when needed). Columns of several sheets with one header and type share an attribute type. Extend mode maps sheets, columns and lookups onto the draft's existing types, attributes and records first. Proposing again recognises its earlier proposals: what is still the engine's follows a newer workbook, what the administrator changed is kept and a column read otherwise is reported (INFERENCE_DISAGREESalso among the profile's warnings); an undone proposal is not applied again. W1 goes from upload to a published change set by applying its proposals. The optional assistant is an interface only (INFERENCE_ASSISTANT,INFERENCE_ASSISTANT_ROWS), off by default. -
Base 0.10 A6a:
missing: "create"on a flag set and on a related record'sparent, and arecord: "existing"row finding the record another source of the same file creates (D93); the kindunmapColumn, andaddValuesToListon flag columns and related records; a profiled column's most frequentvalues. -
Base 0.10 A5a: a model draft's import profile, edited by operations (D74–D80; J34.2, J34.7, J34.8), through the API. A draft holds one profile, read by
GET /model-drafts/{id}/profilewith the import profile it stands for and its warnings against the draft's model. Eleven source and mapping kinds onPOST /model-drafts/{id}/operationsedit it (setProfile,addSource,setSource,removeSource,ignoreSheet,mapColumn,setColumnReading,splitColumn,ignoreColumn,addValuesToList,setValueMap), each one history step undone exactly. The model kinds carry the profile with them: a key change rewrites it, a moved attribute's column moves to the target type's source on the same sheet, a split starts a source of the new type joined by the split's relation, a merge re-points the merged type's sources, an attribute turned into a list type becomes a lookup creating its values, and what a removal takes away is kept as an unmapped column with aCOLUMN_UNMAPPEDwarning. A step that can change what the load writes queues the draft's dry run with its profile (dryRunin the answer; not after labels, the release identity, layout, navigation or consumer API steps). Installing the draft saves its profile as the instance profile of its key, checked against the new release (importProfilein the answer). -
Base 0.10 A5a:
missing: "create"on an import profile'sparentandrelationbindings matched by name: a cell naming no record creates one by its name, once per value per load, before the records naming it (D77);CREATE_NEEDS_NAME_MATCHrefuses it with any other match. -
Base 0.10 A3: source files, their profiles and the dry run (D65–D71; J34.1, J34.3), through the API.
POST /source-fileskeeps an XLSX or CSV file for a model draft or an import profile (inJOB_FILE_DIRas{id}.upload, underIMPORT_MAX_BYTES, deleted with its owner; an upload supersedes the owner's earlier file, deletedJOB_RETENTION_DAYSlater) and queues aprofilejob, which stages every filled cell in PostgreSQL once and profiles it with set statements:GET /source-files/{id}/profile/sheetslists each sheet's blocks with their header rows (D68),…/sheets/{sheet}/columnseach column's filled and distinct counts, value types, candidate key, delimiters, placeholders (D70) and overlaps with other columns and, in a named workspace, with the model's record names and unique text attributes (D69).POST /model-drafts/{id}/dry-runruns the import preview of the draft's file through an inline profile (D65) against the draft's compiled model, binding the staged cells (D66), over every item (D67), as adryRunjob that writes nothing;GET …/dry-runreads its summary per source with the difference from the previous one,GET …/dry-run/rowsthe rows behind a figure (source,status,code). A dry run of a draft changed since is cancelled, as is a running one when another is sent; a draft that does not compile is refused422 INVALID_MODEL(D71).POST /importstakessourceFileIdin place of the file part. The synthetic workbooks W1, W1′, W2 and W3 are generated bytests/workbooks.py. -
Base 0.10 A5b: the model editor with a workbook (D81, D84; J34.1–J34.3, J34.7, J34.8). New from a workbook on Settings › Model creates a draft with the workbook and opens it; an open draft takes one, or a newer one, from its ⋯ menu. A source pane left of the model pane shows the sheets as tabs and each column with its status and statistics; a selected column shows its mapping in the centre (what it feeds, its reading, match, split, created values, value map, key, refused rows and statistics), and the type it feeds is marked in the model pane. Map to… (from the column, by Shift+F10, or by dragging the column onto a type) maps a column to a name, an attribute or a new one, the parent, a related record or a flag of a relation, or ignores it, starting the sheet's source where needed, in one step. A value map starts from the column's placeholders. The drawer gains a Dry run tab with each figure and what it was, opening the rows behind it; the Issues tab lists the profile's warnings. Load into a change set… installs and activates the draft when its model changed (installing an unchanged one only saves the profile), opens a change set named after the file, loads the stored file through the saved profile and opens the review.
test:browser:authoring-a5walks it on both designer models.
Changed
-
Base 0.10 A5c (D105): a relation column onto a relation its record holds one of (many-to-one from its side, or one-to-one) is mapped with
replace: trueby the mapping kinds, Map to… and the inference, so the next version's changed value switches the record instead of being refusedCARDINALITY_VIOLATION. The dry run's summary countsdeletedrecords andmissingRecords; the binding reportmissingRecords. -
Base 0.10 A6b: an operation of a proposal holds when the binding has what it sets, so proposing again no longer re-applies a binding a value map was added to (found by the browser runner: the name drift's map was dropped and the dry run refused the drifting rows). The editor's marks name the proposal that created an element, not the last one naming it. Add a workbook and New from a workbook share one workspace field (
WorkspaceField); a dialog lays two short fields side by side (dialog-pair); the model pane scrolls a selection made elsewhere into view. -
Base 0.10 A6a: profiling analyzes the staged cells once they are copied in, so the statements over them are planned for their real size: the inference of W1 at 10,000 rows took 54.6 s before and 0.35 s after (profiling unchanged at 6 s). The step answer of the operation, undo, redo and application routes is one function (
management/steps.py). -
Base 0.10 A5b: the editor keeps the selected element selected after a step that changes its key (the address used to keep the old key, and the centre lost it). The target list of Move to… is shared with Map to… (
TargetList), the install dialog's release identity with the load (ReleaseIdentityFields), and the import job's rows table with the dry run's (JobRowsTable). -
Base 0.10 A3: D9's interactive dry-run budget (1 s for 10,000 rows, a sample above it) is replaced by D67, at the user's decision: every dry run checks the whole file. Measured once: profiling W1 at 10,000 rows takes 6.3 s, its dry run about 7 s (19,453 items) after the fix and A3a below.
-
Base 0.10 A3a: a trial run (an import's or a batch's preview, a dry run) records no audit entry and takes no feed position (D72), and relation sets advance in one array statement per thousand sets for every write (D73); the trial run of a 10,000-row workbook takes 5.75 s instead of 11.8 s, and holds the workspace's write lock that much shorter.
Fixed
-
Base 0.10 A6 (found by A8b's case 10): an element the administrator created again exactly as a proposal had made it (a list removed and loaded again) read as still proposed, and was marked Proposed and followed by Propose again. A step other than a proposal's that creates an element after its proposal now makes it the administrator's, as D92 says. Regression:
test_a_rectangle_is_read_as_a_list_on_request(J34.10, seen failing without the fix). -
Base 0.10 A5, A6 (found by A8a's sample-file smoke): a column whose header begins or ends with a space could not be mapped in a draft (
unknownElement), since the draft's sheets kept the spaces the import trims. The draft's sheets name a column as the import reads it. -
Base 0.10 A6 extend mode (found by A7's browser runner): a workbook the editor uploads names no workspace, and its columns were then compared with no records, so additions to the model matched nothing by value from the editor. A file without a workspace is compared with the default workspace's records, as its profile job already ran there.
-
Import binding (0.9 J2, found by 0.10 A5c's 10,000-row measurement): finding each row's record by its key attribute, and a match by attribute, compared every row with every record of the context (21.9 s for 10,000 rows into a workspace holding them); they now join on a table of the values a key or a match reads, a hash join (the whole binding of W1′ at 10,000 rows: 2.4 s).
-
Base 0.10 A6b (reported on a workbook): a workbook with nothing to propose got the new application alone, which left the draft refusing an application without types; now nothing is proposed and the inference says why. Load into a change set and Install and activate on a draft that does not compile blanked the page (its compilation has no changes); both now say the draft has errors, and Load says when nothing is mapped yet. A sheet with a note or small block beside its table is now read by its table. A page part failing while rendering shows what failed and a reload under the top bar instead of an empty page.
-
Base 0.10 A6b, on workbooks (D99–D103): the proposed model no longer refuses rows of its own workbook. A type gets one parent (a further repeated column is a list it refers to), optional where rows leave it empty (
setContainment'sparent.optional), and a source reads no record from a row holding nothing in its columns. Lists of a GUID and a name are proposed; translations are found by their values whatever their headers; a sheet's yes/no columns form one set; descriptions in sentences are long text, never a parent; names are written as words, a type named after its key column. A second column with an earlier column's header is left out with a warning. -
Base 0.10 A6b: New from a workbook offers A new model: the workbook's application in a draft that starts empty, without the active model's applications ("A new application" adds it to the model the draft starts from).
-
Base 0.10 A6b: compiling a draft that does not compile sends
changesempty instead of leaving it out, so the answer matches its type in clients and their test fakes. Browser runners fail on any uncaught page error or a page part the error boundary replaced, in every page a run opens. -
Binding an import whose profile has a relationship column grew with rows times links: the record items looked up each row's required links in a subquery run per row. They are collected once with a grouped join; a 10,000-row file binds in 1.4 s instead of 9.3 s.
-
GET /jobs/{id}/rowsserves a dry run's rows too and takessourceandcodeinternally for the dry-run rows route; the job list'skindfilter takesprofileanddryRun.