ddcore 0.20
0.20.3 — 2026-09-28
Added
- A site-wide
ddcorerange inddcore.json. A site whose apps live in one repository declares the ddcore releases it is tested against once —"ddcore": ">=0.20.0 <0.21.0"— instead of repeating it in everydefineApp. It is checked on every load with the apps' own ranges (site requires ddcore … (ddcore.json), but this binary is …), shown byddcore doctor, and written byddcore init;ddcore new-appin such a site gives the app no range of its own. To adopt it, move the range intoddcore.jsonand delete theddcore:line from the apps that only this site ships. See "The site's range" indocs/agent/conventions.md.
Fixed
ddcore.jsonis written without HTML escaping, so a range saved byinitornew-appreads>=0.20.0, not\u003e=0.20.0.
0.20.2 — 2026-09-28
Added
- Cell clicks on form grids, and
frm.setRowValue(#21). A form script'sgrids.<table>.onCellClick.<child field>(frm, row)turns that column's read-only cells (or every cell of agridEditMode: "dialog"grid) into buttons; the handler gets the row clicked, whatever the grid's sort or filters.frm.setRowValue(table, row | id, field | {..}, value)changes a row so the grid, the dirty state and the table'sonChangefollow, as an edit in the grid does. A Table'sonChangenow receives the row itself as a fourth argument,(frm, cdt, cdn, row), which reaches a row not saved yet (it has no id). All of it is documented indocs/agent/form-api.md.
Fixed
- Editing a row in a grid's row dialog passed an undefined
cdnto the table'sonChange; it now passes the row's id (and the row). frm.addChildreturns the row as the form holds it, so a change made through the returned object reaches the grid.
0.20.1 — 2026-09-26
Added
Preset row filters on form grids (#19). A
TableorReportfield takesgridFilters: [{ label, filters, default? }]: toggles above the grid, each showing only the rows that match its list-style filters ([["in_class", "=", 1]]or{ in_class: 1 }). They run in the browser on the loaded rows, so they work oncomputedcolumns too. Toggles that are on combine with AND;default: trueturns one on when the form opens. Display only, likegridSort: rows and theiridxare untouched. Selection, "select all", export and a Report's totals follow the filtered rows. Labels are catalogue keys. The fields a Table's filters name, and their operators, are checked at load; tree operators are refused.gridIndex: falsehides a Table grid's#column (#20), for a grid sorted by something other thanidx, where each row's stored position means little on screen. The column still shows by default. It can be set fromextendDoctypeand fromfrm.setDfProperty; on any field other than a Table it fails validation.Sortable, exportable, selectable form grids (#18). A
Tablefield takesgridSort: { field, order }(the default display order),gridSortable(click a column header to sort),gridExport(CSV and XLSX buttons, for users with theexportpermission) andgridSelect(row checkboxes, "select all" and "Delete selected"). Sorting is display only: a row'sidxstays what was saved. Export sends the selected rows, or all of them, in the order on screen, with Link titles and Select labels. The properties can be set fromextendDoctypeand fromfrm.setDfProperty.Computed fields (#18).
computed: truedeclares a field with no column, never stored and always read-only, on a DocType or a child table. The controller'sonLoad(doc)sets its value whenever a document is loaded for a form, a print, or the response to a save or a method. Only the computed fields survive the hook. A computed field cannot be filtered or sorted on in a list.Reportfieldtype (#18).{ fieldtype: "Report", options: "<report name>", reportFilters: { <filter>: "<field or id>" } }shows adefineReportrun for the document as a read-only grid in the form. It honoursgridSort,gridSortable,gridExportandgridSelect, keeps the report's roles and permissions, and re-runs after a save, a reload orfrm.refreshField(fieldname). A report name that does not exist fails the load.XLSX export and column sorting on the report page. The report view exports XLSX as well as CSV, and sorts by a click on a column header.
ddcore.report(name, filters)in desk scripts, andfrm.refreshField(fieldname).
Changed
Lists hide a hash id by default. A DocType whose id is a random hash (
idGeneration: { hash: true }, or no rule) no longer shows the leading ID column in its list view (nor in the page CSV export); the title field's cell links to the document instead. A list with no other column keeps it.defineListView(..., { idColumn: true })brings the column back.onLoadin a controller now runs. It was declared in the SDK types but never called; it now fills computed fields as described above. An app that declared it only for documentation should check what it does, since any change to a field that is notcomputedis discarded.The report page's CSV button moved from the page header to a toolbar above the table, next to the new XLSX one.
0.20.0 — 2026-09-24
Added
- Virtual DocTypes (DAT-07, #17).
defineDoctype({ virtual: { sources: [...] } })declares a DocType with no table whose rows are the union of other DocTypes, each source mapping its fields onto the virtual ones. Each source is read through its own permissions (role rows,permissionQuery, User Permission scopes, shares and field levels), so a user sees a row exactly when they can read the source document. A field above the user's level on that source reads as null. A row's id, and the value a Link to the virtual DocType stores, is"<Source>:<id>". Such a Link is checked against the source on save, blocks deleting a source document that is still referenced, and follows renames of the document and of the source DocType. Asource_doctypefield is added. All writes (insert, save,dbSet, delete, rename, import) are refused. A virtual DocType is kept out of global search unless it setsglobalSearch: true, and it cannot be theallowof a User Permission. In the Desk, opening a row, a Link cell, a Link control's ↗ or a search hit sends you to the source document, and the Link picker labels each option with its source.
Changed
- The desk shows a spinner while it loads. The boot screen, the workspace redirect, a form still fetching its document and a portal page used to show a bare
…in the corner; they now show a centred loading spinner.
Fixed
- A Table MultiSelect's list stays open after a pick. Clicking an option used to take the focus out of the input and close the list, so each value needed another click in the field. The list now stays open and offers what is left.