ddcore 0.19
0.19.5 — 2026-09-24
Added
Table MultiSelect field. A field that holds several links to one DocType, such as tags, categories or regions. It is stored as child rows of a child DocType that has exactly one Link field, like Frappe's field of the same name, and the desk edits it as pills with a search to add more. The value is rows in every response, but a write may send plain ids (
["A", "B"]); a value already held keeps its row. Empty and repeated values are refused, and each value is checked like any Link, User Permission scopes included. Print shows the values on one line and the history shows them as values. Switching a field betweenTableandTable MultiSelectneeds no migration. Seedocs/agent/fieldtypes.md.Role names show translated. Role is
translateId, a new DocType flag: the desk shows the id through the catalogue wherever it shows a title — a Link, a grid cell such as the User's roles, the list, the form header — and a Link search also matches the translated text. The stored value stays the English id, so permissions andHasRoleare unchanged. Each role an app declares indefineApp({ roles })is now a catalogue key: runddcore i18n extractand translate them, ormake checkreports them missing. Seedocs/agent/i18n.md.The calendar's month is in the URL. Moving the calendar to another month sets
?month=YYYY-MM, so a reload, a shared link or the back button returns to that month.Calendar records span their days. A calendar with
endFieldnow draws each record as a bar from itsfieldday to itsendFieldday, wrapping at the end of each week, and loads the records that overlap the month rather than only those that start in it. A record with no end still takes one day.endFieldwas accepted before but ignored. Seedocs/agent/form-api.md.A Color field paints the boards. When a Calendar, Gantt or Kanban
colorFieldnames a Color field, each record is drawn in the colour it holds — the hue behind a calendar entry or a Gantt bar, a coloured left edge on a Kanban card — instead of a palette colour hashed from the hex. A SelectcolorFieldis unchanged. Seedocs/agent/form-api.md.A Color field picks from a palette. The field now shows only its colour, without the hex beside it. Clicking it opens a picker on a grid of basic colours; its Advanced tab holds a full picker drawn in the page — a saturation/brightness square, a hue bar, R, G and B boxes, the
#rrggbbtext box, and an eyedropper where the browser offers one. Clear moved into the picker.
Changed
- A Link's option list fits its options. The typeahead under a Link field used to be exactly as wide as the input, so a narrow grid column cut every option off. It is now at least that wide and grows to fit the longest option, up to 420px.
Fixed
- A Link's option list scrolls. The mouse wheel no longer snaps the list back to the top, and the arrow keys scroll the chosen option into view. The list is again capped at its 260px height instead of reaching the edge of the window.
0.19.4 — 2026-09-24
Added
- Tasks open under the To-Do page.
/app/todo/newand/app/todo/<id>show the ToDo form there, instead of under whatever workspace ToDo belongs to; the page's list, calendar and board link to them. - Undated tasks on today. The To-Do calendar shows the tasks without a due date on today.
GET /api/todo/pendingtakesundated=1to keep them alongsidedate_from/date_to. - Status buttons on the ToDo form. An open task offers Complete and Cancel in the header, a closed or cancelled one Reopen; the assignment calls from a form script now also refresh the sidebar's To-Do count.
Changed
- Clicking a calendar day lists that day's records — on the To-Do page and in a DocType's calendar view — instead of opening a new one; the new record is the + in the day's corner, shown on hover. A filter set this way shows in the filter bar even when the field is not a standard filter.
- ToDo's Status is read-only in the form; it moves through the buttons, the To-Do page and the assignment endpoints.
Fixed
- Saving a User no longer erases its password. A read blanks
password_hashand the other secret fields, and the desk sent that null back on save, which cleared the password and signed the user out.PUT /api/resource/...and the save/submit/cancel methods now keep the stored value of a Password or secret field that arrives null; a new value still applies. - Changing a User's type takes effect at once.
user_typewas cached with no expiry, so a Website User promoted to System User stayed confined to the portal until a restart. Saving, renaming or deleting a User now drops it. - The To-Do list keeps its columns readable. A long description wraps between words instead of splitting them letter by letter next to a reference that would not wrap; the reference wraps too, the priority, due date (now in the site's date format), status and actions stay on one line, and a narrow screen scrolls the table sideways rather than squeezing it.
0.19.3 — 2026-09-24
Added
- Discard changes when leaving a form. The "Unsaved changes" dialog shown on leaving a dirty form has a Discard changes button, also on the
Deletekey (⌫ on a Mac), that throws the draft away and leaves. Dialogs get the same throughdangerShortcut: trueonddcore.ui.Dialog, which bindsDeleteandBackspaceto the danger action (never while typing in a field), and addcore.ui.confirm(..., { destructive: true })confirms on it. - The To-Do page reads like a DocType list.
/app/todois now a sortable table (description, reference, priority, due date, the other party, status) with a filter bar — search, status, priority, due date, and the assigner or assignee — a page-size choice, and Calendar (by due date) and Kanban (by status) views. Dragging a card completes, revokes or reopens the task. The filter bar opens from a Filters button, which counts the active filters; it starts closed unless the URL carries filters. Filters and the view are kept in the URL. POST /api/assignments/reopen(ddcore.assignments.reopen(id)) sets a closed or cancelled ToDo back toOpen, with a timeline comment.GET /api/todo/pendingfilters and sorts:user,priority,date_from,date_to,no_date,qandorder_by;limitgoes up to 500, and the response carries the users' linktitles.- ToDo list columns. The generic ToDo list (System → ToDo) shows the description, status, priority, due date, assignee, assigner and reference, with colours for status and priority.
- A DocType picker for a Dynamic Link's type field. A
Datafield named in a Dynamic Link'soptionsis shown in the desk (form, grid and list filters) as a list of the DocTypes the user can see, and changing it clears the link, whose document belonged to the old DocType.
Changed
- ToDo's
reference_idis a Dynamic Link onreference_type. The ToDo form picks the reference from lists instead of text boxes; a save naming a DocType or a document that does not exist is refused, and lists show the referenced document's title. Deleting the referenced document still deletes its ToDos rather than being blocked by them. The type field is labelled "Reference Type" instead of "Reference DocType".
Fixed
- Importing a DocType with a Dynamic Link failed with "expected 0 arguments, got 1" while checking for dangling links.
- A Select shows a stored value outside its options (a DocType the user cannot see, a retired choice) instead of rendering blank.
- To-Do reference links open the referenced document in its workspace; they pointed at a route that does not exist.
0.19.2 — 2026-09-23
Added
linkSubtitleon a DocType picks the fields a Link dropdown shows under each title, such aslinkSubtitle: ["cpf"]to show the CPF without the random id. Left out, the line is unchanged (the id and thesearchFields). The fields must exist and be permlevel 0, andextendDoctypecan set them. Seefieldtypes.- Quick create from a Link field. An empty Link shows a + inside the input when the user may create the target. It opens a dialog with the target's title and required fields, prefilled with the typed text, and selects the new document. A target with a required child table opens its full form in a new tab instead.
portal.includeindefineApploads app client scripts on portal pages, served as/assets/apps/<app>/portal.js. Use it for input masks or a lookup on a portal form. A Website User gets them on every portal page, and a desk user gets them on first entering My portal. The boot'sportalIncludesnames the apps that declare one. Form scripts (*.form.ts) still do not run in the portal. Seeportal.
Changed
- The Link dropdown is denser: tighter rows, the subtitle right under the title, and long titles cut with an ellipsis.
- Notification center actions. Each notification has an icon button to mark it as read or unread, and an Open document button that first asks "Mark as read?" (Yes by default). Read notifications are greyed out.
/api/notificationslists unread notifications first, newest first within each group, so read ones sink to the bottom across pages.
0.19.1 — 2026-09-23
Fixed
- A document's title in a form confirmation is escaped. 0.19.0 put the title into the "Approve …?", "Delete …?", "Submit … permanently?" and "Cancel …?" dialogs, whose message is rendered as HTML, so a title holding markup (a name typed as
<img onerror=…>) ran script for whoever opened the dialog. The title is now escaped. Upgrade from 0.19.0.
0.19.0 — 2026-09-23
Breaking
- Website Users are confined to the portals.
User.user_type = "Website User"used to be stored and ignored. Now such a user signs in to/portal, is sent there from any desk path, and gets 403 from every/apiroute that is not the portal's: resources, meta, search, reports, workspaces, notifications, events, and methods not whitelisted withportal: true. Guest is unaffected. Upgrade: list them withddcore eval 'ddcore.db.getAll("User", { filters: { user_type: "Website User" }, fields: ["id"] })'and set any that should keep the desk toSystem User.
Added
- Portals (OPS-10).
definePortalinportal/<name>.portal.tsdeclares a self-service portal for Website Users.rolessays who reaches it.identityfinds the signed-in person's own record (e.g. the Employee whoseuseris them), and pages over DocTypes eachmatchtheir rows to that identity, with the fields shown, the fields editable, and whether the page creates or writes. The pages are a Website User's only grant, enforced inside the permission check, so lists, reads, inserts, saves, uploads and downloads all follow them. The workflow, controller hooks and scopes still apply after them. The desk serves/portalwith its own layout, list, record and form screens, and the account screen without API keys./api/portal/*serves the pages;/api/bootlists them asportals, and a desk user who reaches a portal finds "My portal" in the account menu. Seedocs/agent/portal.md. whitelisted(fn, { portal: true })makes a method callable by Website Users. It runs in portal mode, so itsddcore.getList/getDocsee what the portals grant. The core's own profile, password, session and language methods are marked.ddcore.users.invite({ email, fullName, roles, userType })andddcore.users.resendInvite(user)let app code invite its portal users. Without System Manager, they can only create a Website User, never with a privileged role.core.services.users.invite/resendInvitenow go through them.portalinddcore.json(writesPerHour60,uploadsPerHour30,maxUploadMB10) limits each Website User's portal writes and uploads. Past a limit the answer is 429 withRetry-After.
Changed
- The form's confirmations name the document by its title. Applying a workflow action, deleting, submitting and cancelling asked about the id — a random hash on DocTypes with
idGeneration: { hash: true }. They now use the title the form's header shows, and a workflow action readsApprove "Maria - ID card"?. - A file picked on a document not saved yet is attached when the document is saved. It was uploaded detached and stayed readable only by its uploader and System Manager, so a reviewer could not open an attachment on a document they could read. Each Attach value, including child rows, now claims the detached file it names if the saving user uploaded it.
POST /api/loginreturnshome,/portalfor a Website User and/appotherwise. Single sign-on sends a Website User to/portaltoo.
Fixed
migratecreates roles added to an app that is already installed. Roles declared indefineApp({ roles })were created only on the app's first install, so a role added later never existed and granting it failed with "Role … does not exist". Every migrate now creates the declared roles that are missing.- An engine whose apps fail to load closes its database pool.
engine.Newused to return the error with the connections still open, which kept a test's database busy for whatever ran next. POST /api/uploadchecks write on the document it attaches to. Any signed-in user could hang a file on any document by id, and the file then followed that document's read permission. An upload naming a document now needs write on it. One naming only a DocType, or an id that does not exist yet, needs create or write on the DocType.