pgkiln 0.45.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/db/migrations/106_form_designer.sql +102 -0
  2. package/db/migrations/107_app_version.sql +9 -0
  3. package/db/migrations/108_sql_history.sql +25 -0
  4. package/docs/apex-feature-parity.md +33 -4
  5. package/docs/guide/03-builder.md +123 -28
  6. package/docs/guide/04-pages-and-regions.md +74 -2
  7. package/docs/guide/05-items.md +14 -2
  8. package/docs/guide/08-security.md +1 -1
  9. package/docs/guide/09-reference.md +1 -1
  10. package/docs/guide/10-tutorial.md +1 -1
  11. package/docs/guide/11-from-apex.md +2 -1
  12. package/docs/guide/12-development.md +26 -3
  13. package/docs/guide/16-files.md +2 -2
  14. package/examples/hr/hr_61_form_designer.sql +80 -0
  15. package/package.json +1 -1
  16. package/public/app.css +62 -2
  17. package/public/app.js +244 -35
  18. package/public/builder.css +21 -1
  19. package/public/builder.js +67 -0
  20. package/public/sw.js +2 -1
  21. package/public/worksheet.css +244 -0
  22. package/public/worksheet.js +665 -0
  23. package/src/app.ts +9 -1
  24. package/src/builder/ai-builder.ts +3 -3
  25. package/src/builder/appwizard.ts +22 -6
  26. package/src/builder/arrange.ts +33 -12
  27. package/src/builder/code-editor.ts +1 -1
  28. package/src/builder/components.ts +1 -1
  29. package/src/builder/designer.ts +44 -17
  30. package/src/builder/errors.ts +65 -0
  31. package/src/builder/form-designer.ts +229 -0
  32. package/src/builder/locks.ts +6 -0
  33. package/src/builder/newapp.ts +2 -1
  34. package/src/builder/object-actions.ts +166 -0
  35. package/src/builder/object-browser.ts +111 -33
  36. package/src/builder/querybuilder.ts +1 -1
  37. package/src/builder/region-help.ts +2 -0
  38. package/src/builder/region-settings.ts +2 -0
  39. package/src/builder/reporter.ts +1 -1
  40. package/src/builder/routes.ts +7 -5
  41. package/src/builder/sampledata.ts +2 -2
  42. package/src/builder/scripts.ts +3 -3
  43. package/src/builder/search.ts +3 -3
  44. package/src/builder/shared.ts +2 -2
  45. package/src/builder/sql.ts +15 -73
  46. package/src/builder/table-editor.ts +219 -0
  47. package/src/builder/ui.ts +5 -3
  48. package/src/builder/unload.ts +8 -4
  49. package/src/builder/worksheet.ts +569 -0
  50. package/src/builder/workspaces.ts +1 -1
  51. package/src/cli/replace.ts +10 -0
  52. package/src/i18n/ar.ts +62 -0
  53. package/src/i18n/cs.ts +62 -0
  54. package/src/i18n/da.ts +62 -0
  55. package/src/i18n/de.ts +62 -0
  56. package/src/i18n/el.ts +62 -0
  57. package/src/i18n/es.ts +62 -0
  58. package/src/i18n/fi.ts +62 -0
  59. package/src/i18n/fr.ts +62 -0
  60. package/src/i18n/he.ts +62 -0
  61. package/src/i18n/it.ts +62 -0
  62. package/src/i18n/ja.ts +62 -0
  63. package/src/i18n/ko.ts +62 -0
  64. package/src/i18n/nb.ts +62 -0
  65. package/src/i18n/pl.ts +62 -0
  66. package/src/i18n/pt.ts +62 -0
  67. package/src/i18n/ru.ts +62 -0
  68. package/src/i18n/sv.ts +62 -0
  69. package/src/i18n/tr.ts +62 -0
  70. package/src/i18n/uk.ts +62 -0
  71. package/src/i18n/zh.ts +62 -0
  72. package/src/i18n.ts +124 -0
  73. package/src/metadata.ts +4 -2
  74. package/src/runtime/devbar.ts +1 -1
  75. package/src/runtime/facet-state.ts +10 -5
  76. package/src/runtime/facets.ts +29 -5
  77. package/src/runtime/form-designer.ts +676 -0
  78. package/src/runtime/grid.ts +15 -8
  79. package/src/runtime/items.ts +33 -21
  80. package/src/runtime/lists.ts +1 -1
  81. package/src/runtime/pdf.ts +2 -1
  82. package/src/runtime/regions.ts +4 -0
  83. package/src/runtime/render.ts +7 -3
  84. package/src/runtime/report.ts +2 -2
  85. package/src/runtime/routes.ts +3 -1
  86. package/src/runtime/smart-filters.ts +2 -1
  87. package/src/runtime/tasks.ts +6 -3
  88. package/src/sqlformat.ts +208 -0
  89. package/src/sqlplan.ts +133 -0
  90. package/src/sqlscript.ts +6 -2
  91. package/src/sqlworksheet.ts +278 -0
  92. package/src/tabledef.ts +226 -0
  93. package/src/unload.ts +14 -3
@@ -819,7 +819,9 @@ Counts take the search and all *other* facets into account. Three kinds of facet
819
819
 
820
820
  - **checkbox** (the default): the most frequent values of a column, each with a checkbox; after the seventh
821
821
  the rest sit under *Show all (n)* (open when one of them is chosen), and a boolean's values read *Yes* and
822
- *No*. With
822
+ *No*. Its ⋮ menu (APEX: the facet's menu) has *Clear* (when values are chosen) and *Sort by count* (the
823
+ default) or *Sort by name*; the order is a URL parameter (`r<report id>_fo_<column>=name`) that Apply and
824
+ Reset keep. With
823
825
  `"exclude": true` the facet gets an *Exclude the selected values* switch: the chosen values are
824
826
  then left out instead (rows where the column is empty stay).
825
827
  - **range**: a number or date column in ranges, as radio buttons ("Any", then each range). A
@@ -1127,7 +1129,7 @@ Attributes (the region's **Settings** in the page designer):
1127
1129
  | Key | Meaning |
1128
1130
  |---|---|
1129
1131
  | `list` | The list's name (upper case) |
1130
- | `template` | `links` (default: nested links), `badges` (a row of tiles with the badge as the value), `cards` (a card per entry with its description), `tabs` (the top-level entries as tabs) or `wizard_progress` (numbered steps: the entry of the current page is the current step, the ones before it are done and link back; APEX: Wizard Progress) |
1132
+ | `template` | `links` (default: nested links), `badges` (a row of tiles with the badge as the value), `cards` (a card per entry with its description; (sprint 88) a colour band and the icon in a round badge, in the chart palette's order), `tabs` (the top-level entries as tabs) or `wizard_progress` (numbered steps: the entry of the current page is the current step, the ones before it are done and link back; APEX: Wizard Progress) |
1131
1133
 
1132
1134
  The HR example's page 31 (Shortcuts) shows a static list with each template (with a badge from an
1133
1135
  item, child entries, a manager-only entry and one of an excluded build option) and a department
@@ -1198,6 +1200,76 @@ region when an application is replaced. The HR example's page 36 (*My reports*)
1198
1200
  (a view without the user name and photo columns) and leave requests (row level security: an
1199
1201
  employee sees only their own), with King's shared report *Salary by department*.
1200
1202
 
1203
+ ### `form_designer` (Form Designer)
1204
+
1205
+ A **Form Designer** region (sprint 85; pgkiln's own, APEX has no counterpart) lets the application's
1206
+ administrators build their own data entry forms in the running application, without a developer
1207
+ and without a new release. Like the [Data Reporter](#data_reporter-data-reporter), the developer
1208
+ decides *what* may be edited; the administrators decide *how* the forms look and who uses them.
1209
+
1210
+ **The developer** places the region (page designer gallery: *Form Designer*) and, in its
1211
+ **Settings**, chooses **who may design forms** (an [authorization scheme](08-security.md); without
1212
+ one nobody can) and adds **data sources**: a table or view each (pgkiln's own `meta` schema and the
1213
+ system schemas are never offered), with a static id, a label and a description. Per source:
1214
+
1215
+ - the **columns** forms may show and change, with a label and, for a select list or radio group,
1216
+ a **list of values**: a shared one of the application (`LOV:NAME`) or `STATIC:Display;value,…`
1217
+ (never SQL: designers can't write queries);
1218
+ - the **key column** that identifies a row (the primary key of a new table; choose one for a view),
1219
+ needed to change and delete rows;
1220
+ - what forms on it **may** do at most: **add**, **change** and **delete** rows.
1221
+
1222
+ A new source offers all its columns except binary and JSON ones, allows adding and (with a key)
1223
+ changing rows, never deleting. Untick generated keys and audit columns; the builder warns when the
1224
+ application's database role lacks a privilege a source needs.
1225
+
1226
+ **Designers** (users that pass the scheme) see *New form* and a *Design form* link under each
1227
+ form. The designer is a plain form (it works without JavaScript): a name, a description, what
1228
+ users may do (within what the source allows) and a row per offered column: *On the form*,
1229
+ *Order*, *Label*, *Help text*, *Required*, *Display* (a field by the column's type; a text area for
1230
+ text; a select list or radio group for a column with a list of values) and a *Default* for new
1231
+ rows, and *In the list* for the columns of the form's list of rows. A form is a **draft** until
1232
+ *Published* is ticked: only designers see drafts.
1233
+
1234
+ **Users** see the published forms as cards. A form with list columns opens on its rows (newest
1235
+ first, paged, `page_size`) with *Add* and an *Edit* link per row; a form without list columns
1236
+ only adds rows: it opens on an empty form, and after *Save* on the next empty one. Fields follow
1237
+ the column's type: a number field, the [date picker](05-items.md#the-date-picker) in the
1238
+ application's date format for dates, the browser's date and time field for timestamps, a
1239
+ checkbox for booleans.
1240
+
1241
+ How it stays safe:
1242
+
1243
+ - rows are read and written as the **application's database role**: grants and [row level
1244
+ security](08-security.md) apply (in the HR example an employee changes only their own meetings);
1245
+ - a form's definition is checked against the region's settings before it renders or saves: only
1246
+ offered columns that exist and can be read, displays the column's type allows, actions the
1247
+ source allows; anything else is dropped. Only the form's fields are written; identifiers are
1248
+ only the developer's schema, table and column names, always quoted; values and row keys are
1249
+ always parameters; a value of a list of values must be in the list;
1250
+ - **lost updates** are refused: a row changed since its form was opened is not overwritten (the
1251
+ user sees what they typed, with a message);
1252
+ - forms are stored in `meta.user_form`, reached by applications only through the view
1253
+ `meta.user_forms` (the forms of the application) and the functions `meta.save_user_form` /
1254
+ `meta.delete_user_form`; saving rows and forms needs the CSRF token and a region the user can
1255
+ see, designing also the region's authorization scheme. Every saved, changed or deleted row and
1256
+ form is in the activity log (event `form_designer`).
1257
+
1258
+ Attributes (written by the region's **Settings**):
1259
+
1260
+ | Key | Default | Meaning |
1261
+ |---|---|---|
1262
+ | `sources` | | `[{"id": "orders", "label": "Orders", "description": "…", "schema": "sales", "table": "orders", "key": "id", "columns": [{"name": "customer", "label": "Customer"}, {"name": "status", "lov": "LOV:ORDER_STATUS"}], "insert": true, "update": true, "delete": false}]` |
1263
+ | `design_authz` | | The authorization scheme designers pass; none: nobody designs |
1264
+ | `page_size` | `25` | Rows per page of a form's list (5 to 200) |
1265
+ | `empty` | `No data found.` | Text when a form's list has no rows |
1266
+
1267
+ The sources travel with the application export (they are the region's attributes); the forms are
1268
+ data of the installation, like Data Reporter reports: not exported, and they stay with their
1269
+ region when an application is replaced. The HR example's page 45 (*Forms*, under Administration)
1270
+ offers departments, meetings and employees (changing only) to administrators, with the published
1271
+ forms *Departments* and *Book a meeting*.
1272
+
1201
1273
  ### `ai_assistant` (AI assistant)
1202
1274
 
1203
1275
  An **AI assistant** region (APEX: AI Assistant, AI agents and tools) is a chat with an
@@ -11,7 +11,7 @@ page, optionally inside a region. Name them `P<page>_<NAME>` (uppercase letters,
11
11
  | `text` | text field | text |
12
12
  | `textarea` | multi-line text (always full width) | text |
13
13
  | `number` | number field (numeric keyboard on phones) | e.g. `1250.50` |
14
- | `date` | date picker: the browser's, or (sprint 76) with an application date format or a date format mask on the item a text field in that format with a calendar button; typed dates are read back in the format | `YYYY-MM-DD` |
14
+ | `date` | a text field in the item's date format mask, else the application's date format, else `YYYY-MM-DD`, with pgkiln's [date picker](#the-date-picker) (sprint 84; was the browser's); typed dates are read back in the format | `YYYY-MM-DD` |
15
15
  | `datetime` | date-time picker | `YYYY-MM-DDTHH:MM` |
16
16
  | `email`, `tel`, `url` | typed fields with the matching phone keyboard | text |
17
17
  | `password` | password field; never shown back, empty = unchanged | text |
@@ -31,7 +31,7 @@ page, optionally inside a region. Name them `P<page>_<NAME>` (uppercase letters,
31
31
  | `markdown` | Markdown textarea with a toolbar; shown formatted when read-only | the Markdown text |
32
32
  | `rating` | star rating (radio buttons drawn as stars) | `1` … `max` (default 5) |
33
33
  | `combobox` | free text with suggestions from the list of values; several values become tags | **colon-separated** values, e.g. `SQL:Sales` |
34
- | `daterange` | two date pickers, *From* and *To* | `from:to`, e.g. `2026-01-01:2026-06-30` |
34
+ | `daterange` | two date fields, *From* and *To*, in the date format with the date picker (the picker keeps *From* on or before *To*) | `from:to`, e.g. `2026-01-01:2026-06-30` |
35
35
  | `qrcode` | display only: the value as a QR code (SVG drawn on the server) | whatever was set |
36
36
  | `image` | display only: a picture from a URL in the value, or a BLOB from a query ([Display image](#display-image-and-geocoded-address)) | a URL, or nothing |
37
37
  | `address` | a text field with *Check address*: the installation's geocoder offers matching addresses ([Geocoded address](#display-image-and-geocoded-address)) | the address as text |
@@ -51,6 +51,18 @@ where hiredate between nullif(split_part(:P20_PERIOD, ':', 1), '')::date
51
51
  and coalesce(nullif(split_part(:P20_PERIOD, ':', 2), '')::date, 'infinity')
52
52
  ```
53
53
 
54
+
55
+ ### The date picker
56
+
57
+ (sprint 84; APEX: the date picker) A date item, a date range item, an interactive grid's date cell and a
58
+ faceted search's own date range and the task list's vacation rule are text in the date format: the item's date format mask, else the
59
+ application's date format (Settings → Globalization), else `YYYY-MM-DD`. The calendar button next to the
60
+ field (or Alt+↓ in it) opens a month of days under it: the chosen day marked, today outlined, the week
61
+ starting on the language's first day. Arrow keys move a day or a week, Page Up and Page Down a month
62
+ (with Shift a year), Home and End to the week's first and last day, Enter or Space choose, Escape
63
+ closes; *Today* and *Clear* below. Typed dates are read back in the format (and `YYYY-MM-DD` always
64
+ works), so the field works without JavaScript. Timestamp items and grid timestamp cells still use the
65
+ browser's date and time field.
54
66
  ## Properties
55
67
 
56
68
  | Property | Meaning |
@@ -507,7 +507,7 @@ matures. Privileges show up in **SQL Workshop → Object Browser**.
507
507
  A `data_load` process loads files as the app role too, also with a data load definition: a
508
508
  definition naming a table the role can't write fails like any other insert, and a process only
509
509
  finds the definitions of its own application. XML files with a document type declaration are
510
- refused ([chapter 16](16-files.md#data-loading)). The SQL Workshop (SQL Commands, SQL Scripts,
510
+ refused ([chapter 16](16-files.md#data-loading)). The SQL Workshop (the SQL Worksheet, SQL Scripts,
511
511
  Quick SQL, Load Data, Unload Data, Sample Data) runs as the owner instead, so every developer can change any table: keep
512
512
  developer accounts to people you trust with the database.
513
513
 
@@ -427,7 +427,7 @@ navigation entries and application processes have the same `build_option` column
427
427
  | Column | Description |
428
428
  |---|---|
429
429
  | `page_id`, `seq`, `title` | |
430
- | `type` | `report`, `grid`, `form`, `chart`, `cards`, `calendar`, `facets`, `smart_filters`, `display_selector`, `map`, `tree`, `template_component`, `list`, `data_reporter`, `ai_assistant`, `tasks`, `workflows`, `static`, `dynamic` |
430
+ | `type` | `report`, `grid`, `form`, `chart`, `cards`, `calendar`, `facets`, `smart_filters`, `display_selector`, `map`, `tree`, `template_component`, `list`, `data_reporter`, `form_designer`, `ai_assistant`, `tasks`, `workflows`, `static`, `dynamic` |
431
431
  | `source` | SELECT (or HTML for `static`) |
432
432
  | `table_name`, `pk_column` | For `form` and `grid` |
433
433
  | `pk_item` | For `form`: the item holding the key |
@@ -20,7 +20,7 @@ Then open http://127.0.0.1:3100/a/tasks and sign in as `ann` / `ann-password` or
20
20
 
21
21
  ## Step 1: the table and the business logic
22
22
 
23
- Open **SQL Workshop → SQL Commands** and run:
23
+ Open **SQL Workshop → SQL Worksheet**, paste this and press **Run Script** (F5):
24
24
 
25
25
  ```sql
26
26
  create schema tasks;
@@ -169,7 +169,8 @@ Tips:
169
169
  together.
170
170
  - **Errors are hidden by default**: unexpected errors show a reference number unless the app is in
171
171
  debug mode.
172
- - **Dates**: date items use ISO format (`2026-10-01`) and the browser's date picker; format masks
172
+ - **Dates**: date items, date ranges, grid date cells and facet ranges are text in the application's date format
173
+ (else ISO, `2026-10-01`) with pgkiln's date picker; format masks
173
174
  apply to report, grid and cards columns and to number and display items
174
175
  ([number formats](14-globalization.md#number-formats)).
175
176
  - **Modal pages** open over the calling page, and close and refresh it after a successful submit,
@@ -66,7 +66,12 @@ src/
66
66
  sampledata-words.ts built-in name, city, company and word lists of Sample Data
67
67
  unload.ts Unload Data: unloadStatement() (one SELECT), openUnload() (cursor, batches, CSV/JSON/XLSX/XML encoders on Postgres text values)
68
68
  xml.ts safe XML reader (no DTDs or entities, limits) and xmlTable(): rows from a repeating element (unit tested)
69
- sqlscript.ts SQL scripts: splitScript() (statements, line numbers, psql commands), runScript() (stop/continue, transaction, savepoints)
69
+ sqlscript.ts SQL scripts: splitScript() (statements, line numbers and offsets, psql commands), runScript() (stop/continue, transaction, savepoints)
70
+ sqlworksheet.ts (sprint 91) the worksheet's runs: pickStatements() (at the cursor, a selection, all), runStatements() (a log per
71
+ statement, psql command tags, notices, error line and column), explainStatement() (EXPLAIN / rolled-back ANALYZE)
72
+ tabledef.ts (sprint 95) a table as a definition: readTable(), checkTable(), tableSql() (CREATE, or ALTER from the catalog's version)
73
+ sqlformat.ts (sprint 93) formatSql(): the worksheet's Format (clauses per line, keyword case; strings and comments kept)
74
+ sqlplan.ts (sprint 92) EXPLAIN (FORMAT JSON) as a tree: parsePlan() (self time, slowest step, misestimates), planText()
70
75
  quicksql.ts Quick SQL: shorthand parser and PostgreSQL DDL generator (unit tested)
71
76
  xlsx.ts Excel writer for report downloads and Unload Data (typed cells, streamed through fflate's Zip)
72
77
  automations.ts cron parser, next run in a time zone, scheduler (also meta.task_expire_tick, 094), running automations (the actions run
@@ -154,6 +159,8 @@ src/
154
159
  tasks.ts task list region and task actions (approvals); (094) the vacation rule panel and route
155
160
  data-reporter.ts Data Reporter region (migration 057): sources from the region's config, checkDef (offered columns, whitelists),
156
161
  reportQuery/chartQuery, the list and editor (GET form), save/delete routes (meta.save_data_report)
162
+ form-designer.ts Form Designer region (migration 106): sources from the region's config, checkFormDef (offered columns, displays,
163
+ actions), forms as cards, a form's rows, a row's form (lost update hash), the designer; row/design/delete routes
157
164
  workflows.ts workflow console region and its actions
158
165
  ai.ts Generate text with AI: the ai_generate process (config checks, &ITEM. as delimited escaped data,
159
166
  schemas from items, answers into items), aiInputs/aiOutputs for its dynamic action (route in routes.ts)
@@ -209,10 +216,18 @@ src/
209
216
  mail.ts Workspace utilities → Mail services (administrators: providers, write-only secret, allowed senders,
210
217
  applications with From and daily limit, test) and Applications → Activity → Mail (retry, cancel)
211
218
  shared.ts Shared Components and access control
212
- designer.ts page designer: component tree (with computations and branches), layout canvas and gallery, property editor, toolbar
219
+ designer.ts page designer: component tree (with computations and branches), layout canvas and gallery, property editor, toolbar;
220
+ (sprint 86) GET /builder/apps/:app/pages/:no, /builder/pages/:id redirects browsers there (designerUrl in ui.ts)
221
+ errors.ts (sprint 86) the builder's HTML 404 and 500 pages for browsers (wantsHtml, a not-found handler and an onSend hook)
213
222
  arrange.ts page designer layout changes: move, column span, create from the gallery, undo / redo
214
- sql.ts SQL Workshop: the home and SQL commands (templates and drafts to review)
223
+ sql.ts SQL Workshop: the home and the session's schema
224
+ worksheet.ts (sprint 91) the SQL Worksheet (Database Actions' SQL; /sql/commands too): navigator (pickSchema, objects with
225
+ columns, objectStatements), toolbar, output tabs, history (meta.sql_history, 108), plan trees, Run as an app's role, (sprint 93) Save / Rename / Download as SQL Scripts, bind and substitution values, POST /sql/worksheet/format; GET /sql/worksheet/navigator
215
226
  object-browser.ts (sprint 75) the Object Browser: objects by type, system objects hidden, a table in tabs, column actions as SQL to review
227
+ table-editor.ts (sprint 95) Table Properties: GET/POST /sql/objects/table (create, edit ?o=, ?template=), fromForm(), Preview / Apply
228
+ (one transaction) / Open in the worksheet
229
+ object-actions.ts (sprint 94) an object's ⋯ menu (objectMenu) in the worksheet and the Object Browser: Quick DDL (objectDdl), Drop with
230
+ dependents, Add index / trigger / policy as drafts; GET /sql/objects/action, /sql/objects/panel (Open in a panel)
216
231
  scripts.ts SQL Workshop → SQL Scripts: editor, upload/download, run, results per statement, run history
217
232
  quicksql.ts SQL Workshop → Quick SQL page (preview, save as script, run)
218
233
  querybuilder.ts SQL Workshop → Query Builder: catalog, joins by foreign key or drawn, functions/group by, buildQuery() from the URL; the canvas is in builder.js
@@ -241,6 +256,7 @@ src/
241
256
  themeroller.ts Settings → Theme Roller: style variants (add, edit, rename, delete), default style, users may choose
242
257
  subscriptions.ts Shared Components → Subscriptions: subscribe, refresh, unsubscribe, subscribers and publish; the note under a component
243
258
  reporter.ts page designer: Data Reporter settings (sources: table or view, offered columns, labels, masks; sharing)
259
+ form-designer.ts page designer: Form Designer settings (designers' scheme; sources: key, actions, offered columns, labels, lists of values)
244
260
  workingcopies.ts Working copies: list and create, compare with differences, merge or refresh with conflict choices, delete
245
261
  rest.ts REST module endpoints list and curl example (Shared Components)
246
262
  workflows.ts workflow versions, diagram and instances (Shared Components)
@@ -274,6 +290,8 @@ public/
274
290
  app.css theme (light/dark, responsive; --font, --font-size, --radius for style variants; template option classes to-*)
275
291
  app.js client runtime: dialogs (dialog_closed actions), popup LOVs, dynamic actions (focus, classes, messages), grids (add/duplicate rows, master-detail refresh, move/resize columns, copy/paste of cell ranges), menus, lazy regions, maps (Leaflet layers, marker clusters, heat layer, layer legend, area/distance filter) (no inline JS)
276
292
  code-editor.js, .css builder code editor: enhances <textarea data-code>, highlighting, suggestions (no dependencies)
293
+ worksheet.js, .css (sprint 91) the SQL Worksheet: run keys with the cursor, output tabs, navigator swap and search, drag and drop, splitters;
294
+ (92) grid sort, value dialog, history filter; (93) worksheet tabs in localStorage, Files, binds dialog, format, find, case
277
295
  builder.css builder only: IDE look (dark chrome, icon rail, panes), builder light/dark tokens
278
296
  builder.js builder only: tabs, component tree, property filter, drag and drop on the layout
279
297
  builder-icons.svg builder only: icons of the rail, toolbar and designer (b-*)
@@ -293,6 +311,10 @@ test/
293
311
  unload.test.ts Unload Data: CSV/JSON/XLSX/XML output, read back with Load Data, read-only and one-statement checks, streaming
294
312
  app-from-file.test.ts Create → From a file: proposed names and types, app + table + rows + pages, row errors, login, validation
295
313
  workshop.test.ts SQL scripts, Quick SQL pages, query builder, data load definitions (Load Data, the process, export)
314
+ tabledef.test.ts (sprint 95) table definitions round trip: CREATE, read, ALTER, read again; mistakes
315
+ table-editor.test.ts (sprint 95) the Table Properties page: preview, apply, a failing apply rolls back, template, worksheet
316
+ object-actions.test.ts (sprint 94) object menus: Quick DDL, Drop with dependents, Add index / trigger / policy, the panel, not-found
317
+ worksheet.test.ts (sprint 91) the SQL Worksheet: statement at the cursor, Run Script log, plans, notices, history, navigator, Run as
296
318
  quicksql.test.ts Quick SQL parser and DDL generator
297
319
  xml.test.ts XML reader: rows, attributes, paths, refused DTDs and entities, limits
298
320
  printing.test.ts report PDFs
@@ -338,6 +360,7 @@ test/
338
360
  helpers.ts a cookie-keeping test browser
339
361
  e2e/responsive.test.ts browser tests at phone/tablet/desktop widths (Playwright)
340
362
  e2e/code-editor.test.ts the code editor in a browser: highlighting, keys, suggestions, touch, screen readers
363
+ e2e/worksheet.test.ts (sprint 91) the SQL Worksheet in a browser: run keys, tabs, navigator, drag and drop, splitters, phone, no JavaScript
341
364
  e2e/items.test.ts sprint 26 item types in a browser: editors, tags, stars, dates, reveal; without JavaScript
342
365
  e2e/designer.test.ts page designer: panes per width, drag and drop, keyboard, Arrange buttons, builder theme
343
366
  e2e/calendar.test.ts calendar drag and drop and create on click, view switching, chart drill-down
@@ -265,7 +265,7 @@ parsing, type inference and limits.)
265
265
  3. The result shows the rows inserted, updated and skipped, with a link to the table in the
266
266
  Object Browser.
267
267
 
268
- Load Data runs as the builder's owner connection, like SQL Commands.
268
+ Load Data runs as the builder's owner connection, like the SQL Worksheet.
269
269
 
270
270
  ### SQL Workshop → Unload Data
271
271
 
@@ -352,7 +352,7 @@ Rows to develop and test with (APEX 26.1's Data Generator / "sample data for dev
352
352
  Definitions are shared by the developers of the installation, like SQL scripts, and not part of
353
353
  an application export (they describe tables, not an application).
354
354
 
355
- Sample Data runs as the builder's owner connection (like SQL Commands: triggers run, row level
355
+ Sample Data runs as the builder's owner connection (like the SQL Worksheet: triggers run, row level
356
356
  security doesn't apply to the owner), on a connection of its own that is closed afterwards, with a
357
357
  statement timeout (`SAMPLE_DATA_STATEMENT_TIMEOUT`, default `5min`) and at most
358
358
  `SAMPLE_DATA_MAX_ROWS` rows per run (default 100,000). Table and column names come from the
@@ -0,0 +1,80 @@
1
+ -- =====================================================================
2
+ -- HR example, part 61: a Form Designer (docs/guide/04-pages-and-regions.md,
3
+ -- "Form Designer")
4
+ --
5
+ -- Page 45 "Forms": the application's administrators (authorization scheme
6
+ -- ADMIN) build data entry forms on three tables the developer offers in the
7
+ -- region's settings, and publish them for every employee:
8
+ -- - "Departments": hr.dept, add and change rows;
9
+ -- - "Meetings": hr.meeting, add, change and delete rows; its row level
10
+ -- security still applies (an employee changes only their own meetings);
11
+ -- - "Employees": hr.emp, change rows only, with the job and department
12
+ -- as lists of values.
13
+ -- Two published forms are there to start from: "Departments" (a list of
14
+ -- departments with Add and Edit) and "Book a meeting" (no list: the form
15
+ -- only adds rows). Forms are installation data: not part of the export.
16
+ -- =====================================================================
17
+
18
+ insert into meta.page (app_id, page_no, name, title, parent_page)
19
+ select id, 45, 'Forms', 'Forms', 1 from meta.app where alias = 'hr';
20
+
21
+ insert into meta.region (page_id, seq, title, type, columns, template, config)
22
+ select p.id, 10, 'Form Designer', 'form_designer', 12, 'standard', $j${
23
+ "design_authz": "ADMIN",
24
+ "page_size": 15,
25
+ "sources": [
26
+ {"id": "departments", "label": "Departments", "description": "Department names and locations.",
27
+ "schema": "hr", "table": "dept", "key": "deptno", "insert": true, "update": true, "delete": false,
28
+ "columns": [{"name": "dname", "label": "Department"}, {"name": "loc", "label": "Location"}]},
29
+ {"id": "meetings", "label": "Meetings", "description": "Meetings in the planner; employees change only their own.",
30
+ "schema": "hr", "table": "meeting", "key": "id", "insert": true, "update": true, "delete": true,
31
+ "columns": [{"name": "title", "label": "Title"}, {"name": "starts_at", "label": "Starts"}, {"name": "ends_at", "label": "Ends"}, {"name": "private", "label": "Private"}]},
32
+ {"id": "employees", "label": "Employees", "description": "Job, department and salary of employees.",
33
+ "schema": "hr", "table": "emp", "key": "empno", "insert": false, "update": true, "delete": false,
34
+ "columns": [{"name": "ename", "label": "Name"}, {"name": "job", "label": "Job", "lov": "LOV:JOBS"}, {"name": "deptno", "label": "Department", "lov": "LOV:DEPARTMENTS"},
35
+ {"name": "hiredate", "label": "Hire date"}, {"name": "sal", "label": "Salary"}]}
36
+ ]}$j$::jsonb
37
+ from meta.page p join meta.app a on a.id = p.app_id
38
+ where a.alias = 'hr' and p.page_no = 45;
39
+
40
+ -- two published forms to start from (forms are installation data: not part of the export)
41
+ insert into meta.user_form (app_id, region_id, name, description, published, definition, created_by, updated_by)
42
+ select a.id, r.id, f.name, f.description, true, f.definition, 'king', 'king'
43
+ from meta.app a join meta.page p on p.app_id = a.id and p.page_no = 45 join meta.region r on r.page_id = p.id and r.type = 'form_designer',
44
+ (values
45
+ ('Departments', 'Add a department or change its name and location.',
46
+ '{"source": "departments",
47
+ "fields": [{"column": "dname", "label": "", "help": "", "required": true, "display": "auto", "default": ""},
48
+ {"column": "loc", "label": "", "help": "The city.", "required": false, "display": "auto", "default": ""}],
49
+ "list": ["dname", "loc"], "actions": {"insert": true, "update": true, "delete": false}}'::jsonb),
50
+ ('Book a meeting', 'Put a meeting in the planner.',
51
+ '{"source": "meetings",
52
+ "fields": [{"column": "title", "label": "What", "help": "", "required": true, "display": "auto", "default": ""},
53
+ {"column": "starts_at", "label": "", "help": "", "required": true, "display": "auto", "default": ""},
54
+ {"column": "ends_at", "label": "", "help": "Leave empty for a meeting without an end.", "required": false, "display": "auto", "default": ""},
55
+ {"column": "private", "label": "Only visible to me", "help": "", "required": false, "display": "auto", "default": "false"}],
56
+ "list": [], "actions": {"insert": true, "update": false, "delete": false}}'::jsonb)
57
+ ) as f (name, description, definition)
58
+ where a.alias = 'hr';
59
+
60
+ insert into meta.nav_entry (app_id, parent_id, seq, label, icon, target_page)
61
+ select a.id, n.id, 60, 'Forms', 'file', 45
62
+ from meta.app a join meta.nav_entry n on n.app_id = a.id and n.label = 'Administration' and n.parent_id is null
63
+ where a.alias = 'hr';
64
+
65
+ insert into meta.translation (app_id, language, source, target)
66
+ select a.id, 'nl', t.source, t.target
67
+ from meta.app a, (values
68
+ ('Forms', 'Formulieren'),
69
+ ('Form Designer', 'Formulierbouwer'),
70
+ ('Department names and locations.', 'Namen en vestigingsplaatsen van afdelingen.'),
71
+ ('Meetings', 'Vergaderingen'),
72
+ ('Meetings in the planner; employees change only their own.', 'Vergaderingen in de planner; medewerkers wijzigen alleen hun eigen.'),
73
+ ('Job, department and salary of employees.', 'Functie, afdeling en salaris van medewerkers.'),
74
+ ('Title', 'Titel'),
75
+ ('Starts', 'Begint'),
76
+ ('Ends', 'Eindigt'),
77
+ ('Private', 'Privé')
78
+ ) as t (source, target)
79
+ where a.alias = 'hr'
80
+ on conflict do nothing;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgkiln",
3
- "version": "0.45.0",
3
+ "version": "0.47.0",
4
4
  "description": "Open source low-code, SQL-driven application builder for PostgreSQL, inspired by Oracle APEX",
5
5
  "keywords": [
6
6
  "postgresql",
package/public/app.css CHANGED
@@ -1152,6 +1152,13 @@ td[data-add]:hover > .cal-add, .cal-add:focus-visible { opacity: 1; }
1152
1152
  .facet { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
1153
1153
  .facet legend { font-weight: 650; font-size: 0.85rem; margin-bottom: 0.3rem; padding: 0; display: flex; gap: 0.5rem; }
1154
1154
  .facet-clear { font-weight: 400; font-size: 0.8rem; }
1155
+ .facet:has(> .facet-menu) { position: relative; }
1156
+ .facet:has(> .facet-menu) > legend { padding-right: 1.6rem; }
1157
+ .facet-menu { position: absolute; top: 0; right: 0; }
1158
+ .facet-menu > summary { cursor: pointer; padding: 0 0.35rem; border-radius: 4px; color: var(--muted); }
1159
+ .facet-menu > summary:hover, .facet-menu[open] > summary { background: var(--surface-3); color: inherit; }
1160
+ .facet-menu .menu-panel { left: auto; right: 0; min-width: 11rem; }
1161
+ .facet-menu .menu-links a[aria-current] { font-weight: 650; }
1155
1162
  .facet-value { font-weight: 400; font-size: 0.9rem; }
1156
1163
  /* (sprint 65) a facet's chart of its counts, collapsed under the legend */
1157
1164
  .facet-chart { margin: 0.1rem 0 0.4rem; }
@@ -1965,9 +1972,25 @@ a.search-hit:hover, a.search-hit:focus-visible { background: var(--accent-soft);
1965
1972
  .th-menu-values li.muted { padding: 0.35rem 0.9rem; }
1966
1973
  .js th[data-col] > a { cursor: pointer; }
1967
1974
 
1968
- /* (sprint 76) a date item in the application's date format: the browser's picker on a hidden date field */
1975
+ /* (sprint 76) a date item in the application's date format */
1969
1976
  .date-field { position: relative; }
1970
- .date-native { position: absolute; right: 0; bottom: 0; width: 1px; height: 1px; opacity: 0; pointer-events: none; border: 0; padding: 0; }
1977
+ /* (sprint 84; APEX: the date picker) a month of days under a date field (app.js) */
1978
+ .date-picker { position: absolute; top: calc(100% + 4px); left: 0; z-index: 60; background: var(--surface); color: var(--text);
1979
+ border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 8px 24px rgb(16 24 40 / 18%); padding: 0.5rem; min-width: 17rem; }
1980
+ .date-picker-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.35rem; }
1981
+ .date-picker-title { font-weight: 650; text-transform: capitalize; }
1982
+ .date-picker-grid { border-collapse: collapse; width: 100%; }
1983
+ .date-picker-grid th { font-size: 0.75rem; font-weight: 600; color: var(--muted); padding: 0.2rem 0; text-align: center; }
1984
+ .date-picker-grid td { padding: 1px; text-align: center; }
1985
+ .date-picker-grid button { width: 2.2rem; height: 2.1rem; border: 0; border-radius: 6px; background: none; color: inherit; font: inherit; cursor: pointer; }
1986
+ .date-picker-grid button:hover { background: var(--surface-3); }
1987
+ .date-picker-grid button.date-other { color: var(--muted); }
1988
+ .date-picker-grid button.date-today { box-shadow: inset 0 0 0 1px var(--accent); }
1989
+ .date-picker-grid button.date-selected { background: var(--accent); color: var(--accent-text); }
1990
+ .date-picker-grid button:disabled { opacity: 0.35; cursor: not-allowed; }
1991
+ .date-picker-grid button:focus-visible { outline: 2px solid var(--accent); outline-offset: 0; }
1992
+ .date-picker-foot { display: flex; justify-content: space-between; margin-top: 0.4rem; }
1993
+ .js .grid-table tbody td[data-col]:not(:focus-within) .date-field > .btn { visibility: hidden; }
1971
1994
 
1972
1995
  /* (sprint 77) display positions: banner, breadcrumb bar, a side column, footer; button positions in a region */
1973
1996
  .t-pos-banner, .t-pos-breadcrumb { margin-bottom: 1rem; }
@@ -1982,3 +2005,40 @@ a.search-hit:hover, a.search-hit:focus-visible { background: var(--accent-soft);
1982
2005
  @media (max-width: 900px) { .t-columns-left, .t-columns-right { grid-template-columns: 1fr; } .t-columns-right > .t-side { order: 0; } }
1983
2006
  .region-footer-split { display: flex; justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; }
1984
2007
  .region-footer-split > div { display: flex; gap: 0.5rem; }
2008
+
2009
+ /* (sprint 85) the Form Designer region (form-designer.ts): forms as cards with a Design link, the designer's field table, a row's form */
2010
+ .fd-cards > li { display: flex; flex-direction: column; gap: 0.25rem; }
2011
+ .fd-design-link { font-size: 0.85rem; display: inline-flex; align-items: center; gap: 0.3rem; }
2012
+ .fd-fields input.fd-seq { width: 4.5rem; }
2013
+ .fd-fields td select { min-width: 8rem; }
2014
+ .fd-fields td input:not([type=checkbox]):not(.fd-seq) { min-width: 6rem; width: 100%; }
2015
+ .fd-form { margin: 0.75rem 0; }
2016
+ .fd-radios { border: 0; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.25rem; }
2017
+ .fd-new { margin-top: 1rem; }
2018
+
2019
+ /* (sprint 88, GEN-4; APEX: the Cards list template and the hero region) a list's cards each get a colour band
2020
+ and their icon in a round badge, in the chart palette's order; the hero sits on the page background */
2021
+ .list-cards > li { --card-color: var(--series-1); }
2022
+ .list-cards > li:nth-child(8n+2) { --card-color: var(--series-2); }
2023
+ .list-cards > li:nth-child(8n+3) { --card-color: var(--series-3); }
2024
+ .list-cards > li:nth-child(8n+4) { --card-color: var(--series-4); }
2025
+ .list-cards > li:nth-child(8n+5) { --card-color: var(--series-5); }
2026
+ .list-cards > li:nth-child(8n+6) { --card-color: var(--series-6); }
2027
+ .list-cards > li:nth-child(8n+7) { --card-color: var(--series-7); }
2028
+ .list-cards > li:nth-child(8n+8) { --card-color: var(--series-8); }
2029
+ .list-card { border-top: 4px solid var(--card-color); }
2030
+ .list-card-icon { flex: none; display: grid; place-items: center; width: 2.5rem; height: 2.5rem; border-radius: 50%;
2031
+ background: color-mix(in srgb, var(--card-color) 16%, transparent); color: var(--card-color); }
2032
+ .list-card-icon .icon { width: 1.3rem; height: 1.3rem; }
2033
+ .list-card-head { gap: 0.75rem; }
2034
+ .region-hero { background: transparent; padding-left: 0; padding-right: 0; }
2035
+
2036
+ /* (sprint 88, GEN-4; APEX: the page footer) the application's version and Back to top, under the content */
2037
+ .t-footer { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 0.5rem 1rem;
2038
+ margin: 0 1.5rem 1rem; max-width: calc(1500px - 3rem); padding: 0.75rem 0 0.25rem; border-top: 1px solid var(--border); color: var(--muted); font-size: 0.85rem; }
2039
+ @media (max-width: 640px) { .t-footer { margin: 0 0.75rem 1rem; } }
2040
+ .t-footer .t-top { margin-left: auto; display: inline-flex; align-items: center; gap: 0.3rem; color: inherit; }
2041
+ .t-footer .t-top:hover, .t-footer .t-top:focus-visible { color: var(--accent); }
2042
+ .t-footer .icon { width: 1rem; height: 1rem; }
2043
+ @media print { .t-footer { display: none; } }
2044
+ .list-card:hover { border-top-color: var(--card-color); }