pgkiln 0.44.0 → 0.46.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 (91) hide show
  1. package/db/migrations/102_app_computations.sql +345 -0
  2. package/db/migrations/103_shortcuts.sql +341 -0
  3. package/db/migrations/104_report_queries.sql +413 -0
  4. package/db/migrations/105_component_settings_groups.sql +367 -0
  5. package/db/migrations/106_form_designer.sql +102 -0
  6. package/db/migrations/107_app_version.sql +9 -0
  7. package/docs/apex-feature-parity.md +15 -12
  8. package/docs/guide/02-concepts.md +24 -0
  9. package/docs/guide/03-builder.md +69 -13
  10. package/docs/guide/04-pages-and-regions.md +84 -3
  11. package/docs/guide/05-items.md +14 -2
  12. package/docs/guide/06-processing.md +22 -0
  13. package/docs/guide/09-reference.md +14 -1
  14. package/docs/guide/11-from-apex.md +2 -1
  15. package/docs/guide/12-development.md +8 -2
  16. package/docs/guide/16-files.md +23 -0
  17. package/docs/guide/18-cli.md +4 -0
  18. package/examples/hr/hr_61_form_designer.sql +80 -0
  19. package/package.json +1 -1
  20. package/public/app.css +79 -2
  21. package/public/app.js +307 -35
  22. package/public/builder.css +21 -1
  23. package/public/builder.js +67 -0
  24. package/public/sw.js +2 -1
  25. package/src/app.ts +9 -1
  26. package/src/appfiles.ts +18 -2
  27. package/src/builder/advisor.ts +7 -2
  28. package/src/builder/appwizard.ts +22 -6
  29. package/src/builder/arrange.ts +33 -12
  30. package/src/builder/code-editor.ts +7 -2
  31. package/src/builder/components.ts +86 -4
  32. package/src/builder/designer.ts +44 -17
  33. package/src/builder/errors.ts +65 -0
  34. package/src/builder/form-designer.ts +229 -0
  35. package/src/builder/forms.ts +1 -1
  36. package/src/builder/locks.ts +6 -0
  37. package/src/builder/newapp.ts +2 -1
  38. package/src/builder/object-browser.ts +81 -18
  39. package/src/builder/region-help.ts +2 -0
  40. package/src/builder/region-settings.ts +2 -0
  41. package/src/builder/reporter.ts +1 -1
  42. package/src/builder/routes.ts +7 -5
  43. package/src/builder/search.ts +3 -3
  44. package/src/builder/shared.ts +41 -8
  45. package/src/builder/subscriptions.ts +30 -4
  46. package/src/builder/ui.ts +2 -0
  47. package/src/cli/replace.ts +16 -2
  48. package/src/i18n/ar.ts +62 -0
  49. package/src/i18n/cs.ts +62 -0
  50. package/src/i18n/da.ts +62 -0
  51. package/src/i18n/de.ts +62 -0
  52. package/src/i18n/el.ts +62 -0
  53. package/src/i18n/es.ts +62 -0
  54. package/src/i18n/fi.ts +62 -0
  55. package/src/i18n/fr.ts +62 -0
  56. package/src/i18n/he.ts +62 -0
  57. package/src/i18n/it.ts +62 -0
  58. package/src/i18n/ja.ts +62 -0
  59. package/src/i18n/ko.ts +62 -0
  60. package/src/i18n/nb.ts +62 -0
  61. package/src/i18n/pl.ts +62 -0
  62. package/src/i18n/pt.ts +62 -0
  63. package/src/i18n/ru.ts +62 -0
  64. package/src/i18n/sv.ts +62 -0
  65. package/src/i18n/tr.ts +62 -0
  66. package/src/i18n/uk.ts +62 -0
  67. package/src/i18n/zh.ts +62 -0
  68. package/src/i18n.ts +124 -0
  69. package/src/metadata.ts +18 -3
  70. package/src/runtime/component-settings.ts +42 -0
  71. package/src/runtime/context.ts +4 -0
  72. package/src/runtime/devbar.ts +1 -1
  73. package/src/runtime/documents.ts +29 -3
  74. package/src/runtime/engine.ts +2 -2
  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 +36 -11
  79. package/src/runtime/items.ts +47 -32
  80. package/src/runtime/lists.ts +1 -1
  81. package/src/runtime/logic.ts +12 -7
  82. package/src/runtime/pdf.ts +2 -1
  83. package/src/runtime/regions.ts +8 -3
  84. package/src/runtime/render.ts +8 -3
  85. package/src/runtime/report.ts +4 -3
  86. package/src/runtime/routes.ts +7 -3
  87. package/src/runtime/shortcuts.ts +43 -0
  88. package/src/runtime/smart-filters.ts +2 -1
  89. package/src/runtime/tasks.ts +6 -3
  90. package/src/security.ts +2 -1
  91. package/src/subscriptions.ts +67 -0
@@ -114,7 +114,8 @@ src/
114
114
  authz.ts authorization schemes, conditions, visibility (menu requests count as buttons)
115
115
  engine.ts form fetch, validations, processes (conditions, execution chains, queueing background chains; web requests queued by an sql process are made right after it), application processes
116
116
  processes.ts download (file or zip from a query, safe headers), workflow processes, configuration checks of chains
117
- logic.ts computations, branches (page, URL, function returning a URL, another application) and their conditions
117
+ logic.ts computations (the page's and (102) the application's), branches (page, URL, function returning a URL, another application) and their conditions
118
+ shortcuts.ts (103) "NAME" shortcuts in static content, help texts and confirm messages
118
119
  render.ts page chrome (nav, breadcrumb), dynamic action JSON, theme (the page's nonce'd <style>, light/dark and style switches)
119
120
  styles.ts base styles (BASE_STYLES: Iris, Lavender, Standard; baseStyleOf → html data-style), Theme Roller style variants: fixed lists (fonts, sizes, corners), parseStyle/appStyles checks, the request's
120
121
  style (user choice, default), themeCss() (only hex values and constants reach the CSS)
@@ -153,6 +154,8 @@ src/
153
154
  tasks.ts task list region and task actions (approvals); (094) the vacation rule panel and route
154
155
  data-reporter.ts Data Reporter region (migration 057): sources from the region's config, checkDef (offered columns, whitelists),
155
156
  reportQuery/chartQuery, the list and editor (GET form), save/delete routes (meta.save_data_report)
157
+ form-designer.ts Form Designer region (migration 106): sources from the region's config, checkFormDef (offered columns, displays,
158
+ actions), forms as cards, a form's rows, a row's form (lost update hash), the designer; row/design/delete routes
156
159
  workflows.ts workflow console region and its actions
157
160
  ai.ts Generate text with AI: the ai_generate process (config checks, &ITEM. as delimited escaped data,
158
161
  schemas from items, answers into items), aiInputs/aiOutputs for its dynamic action (route in routes.ts)
@@ -208,7 +211,9 @@ src/
208
211
  mail.ts Workspace utilities → Mail services (administrators: providers, write-only secret, allowed senders,
209
212
  applications with From and daily limit, test) and Applications → Activity → Mail (retry, cancel)
210
213
  shared.ts Shared Components and access control
211
- designer.ts page designer: component tree (with computations and branches), layout canvas and gallery, property editor, toolbar
214
+ designer.ts page designer: component tree (with computations and branches), layout canvas and gallery, property editor, toolbar;
215
+ (sprint 86) GET /builder/apps/:app/pages/:no, /builder/pages/:id redirects browsers there (designerUrl in ui.ts)
216
+ errors.ts (sprint 86) the builder's HTML 404 and 500 pages for browsers (wantsHtml, a not-found handler and an onSend hook)
212
217
  arrange.ts page designer layout changes: move, column span, create from the gallery, undo / redo
213
218
  sql.ts SQL Workshop: the home and SQL commands (templates and drafts to review)
214
219
  object-browser.ts (sprint 75) the Object Browser: objects by type, system objects hidden, a table in tabs, column actions as SQL to review
@@ -240,6 +245,7 @@ src/
240
245
  themeroller.ts Settings → Theme Roller: style variants (add, edit, rename, delete), default style, users may choose
241
246
  subscriptions.ts Shared Components → Subscriptions: subscribe, refresh, unsubscribe, subscribers and publish; the note under a component
242
247
  reporter.ts page designer: Data Reporter settings (sources: table or view, offered columns, labels, masks; sharing)
248
+ form-designer.ts page designer: Form Designer settings (designers' scheme; sources: key, actions, offered columns, labels, lists of values)
243
249
  workingcopies.ts Working copies: list and create, compare with differences, merge or refresh with conflict choices, delete
244
250
  rest.ts REST module endpoints list and curl example (Shared Components)
245
251
  workflows.ts workflow versions, diagram and instances (Shared Components)
@@ -604,3 +604,26 @@ name.
604
604
  The standard PDF fonts cover Western European text; set `PDF_FONT` (and `PDF_FONT_BOLD`) for other
605
605
  scripts, as for report PDFs. APEX's Word and Excel templates and outputs have no equivalent: the
606
606
  templates here are HTML and the output is PDF.
607
+
608
+ ### Report queries
609
+
610
+ **Shared Components → Report Queries** (APEX: Report Queries, migration 104) are named queries an
611
+ application prints, without a template: the query's rows as a table.
612
+
613
+ | Property | Meaning |
614
+ |---|---|
615
+ | `name` | Pages download it with `?doc=NAME` (a document template of that name wins); a button with the action *document* names it |
616
+ | `query` | A SELECT with `:ITEM` binds (the page's session state), run as the application's role; its columns' names are the headings |
617
+ | `format` | `pdf` (with the report layout), `xlsx`, `csv`, `json`, `xml` or `html` |
618
+ | `layout` | A report layout for the PDF; empty: the default layout |
619
+ | `file_name`, `title` | The file's name without extension and the PDF's or HTML file's title (`&ITEM.` allowed); empty: the name, and the description or the name |
620
+ | `authz` | Who may download it, besides access to the page |
621
+
622
+ ```
623
+ /a/sales/3?doc=ORDERS_BY_REGION the file, with page 3's session state
624
+ ```
625
+
626
+ The files are made like a download process's [data export](06-processing.md#data-export) (Excel and
627
+ PDF hold at most 100,000 rows) and are sent as attachments that a browser never shows as a page
628
+ (`Content-Security-Policy: sandbox`). Signed out, on a public page of an application with sign-in,
629
+ `?doc=` serves only the report queries and document templates of the page's own buttons.
@@ -65,6 +65,10 @@ hr/
65
65
  app-items/ai_ename.json
66
66
  app-processes/0010-link-user-to-employee.json
67
67
  app-processes/0010-link-user-to-employee.code.sql
68
+ app-computations/0010-ai_region-after_authentication.json (by sequence, item and point)
69
+ component-groups/sales_lookups.json
70
+ report-queries/orders_by_region.json (the query in .query.sql)
71
+ shortcuts/delete_confirm_msg.json (a long value in .value.txt, .html or .sql by its type)
68
72
  lovs/jobs.json
69
73
  lovs/jobs.query.sql
70
74
  report-layouts/hr_directory.json (a logo as hr_directory.logo.png)
@@ -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.44.0",
3
+ "version": "0.46.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
@@ -1056,6 +1056,20 @@ fieldset.prop-group > legend { font-size: 0.75rem; text-transform: uppercase; le
1056
1056
  .grid-table th.grid-drop-after { box-shadow: inset -3px 0 0 var(--accent); }
1057
1057
  /* copy and paste: a range of selected cells */
1058
1058
  .grid-table td.grid-cell-selected { background: color-mix(in srgb, var(--accent) 16%, var(--surface)); box-shadow: inset 0 0 0 1px var(--accent); }
1059
+ /* cell navigation mode (APEX): with JavaScript an editable cell reads as text (its formatted value, .grid-text)
1060
+ until it is clicked or Enter / F2 edits it; arrow keys move between cells (app.js) */
1061
+ .grid-text { display: none; }
1062
+ .js .grid-table td[data-col]:not(.grid-frozen) { position: relative; }
1063
+ .js .grid-table tbody td[data-col]:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
1064
+ .js .grid-table tbody td[data-col] :is(input:not([type=checkbox]), select) { border-color: transparent; background: transparent; box-shadow: none; }
1065
+ .js .grid-table tbody td[data-col] select { appearance: none; }
1066
+ .js .grid-table tbody td[data-col]:focus-within :is(input:not([type=checkbox]), select) { border-color: var(--border); background: var(--surface); appearance: auto; }
1067
+ .js .grid-table td[data-col] > .grid-text { display: flex; align-items: center; position: absolute; inset: 0; padding: 0 calc(0.85rem + 1px);
1068
+ overflow: hidden; white-space: nowrap; pointer-events: none; }
1069
+ .js .grid-table td[data-col] > .grid-text.num { justify-content: flex-end; font-variant-numeric: tabular-nums; }
1070
+ .js .grid-table td[data-col]:has(> .grid-text) :is(input, select) { color: transparent; }
1071
+ .js .grid-table td[data-col]:focus-within > .grid-text { display: none; }
1072
+ .js .grid-table td[data-col]:focus-within :is(input, select) { color: inherit; }
1059
1073
  .grid-columns-form { border-collapse: collapse; font-size: 0.85rem; }
1060
1074
  .grid-columns-form th, .grid-columns-form td { padding: 0.2rem 0.35rem; text-align: left; white-space: nowrap; }
1061
1075
  .grid-columns-form input[type=number] { width: 5.5rem; }
@@ -1138,6 +1152,13 @@ td[data-add]:hover > .cal-add, .cal-add:focus-visible { opacity: 1; }
1138
1152
  .facet { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
1139
1153
  .facet legend { font-weight: 650; font-size: 0.85rem; margin-bottom: 0.3rem; padding: 0; display: flex; gap: 0.5rem; }
1140
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; }
1141
1162
  .facet-value { font-weight: 400; font-size: 0.9rem; }
1142
1163
  /* (sprint 65) a facet's chart of its counts, collapsed under the legend */
1143
1164
  .facet-chart { margin: 0.1rem 0 0.4rem; }
@@ -1216,6 +1237,9 @@ input[type=color] { width: 4rem; min-height: 2.4rem; padding: 0.15rem; }
1216
1237
  .report-reflow.grid-table .agg-row td.grid-lead { display: none; }
1217
1238
  .report-reflow.grid-table td.grid-lead:empty { display: none; }
1218
1239
  .grid-resize { display: none !important; }
1240
+ /* a card shows its fields as fields: no navigation mode */
1241
+ .js .report-reflow.grid-table td[data-col] > .grid-text { display: none; }
1242
+ .js .report-reflow.grid-table tbody td[data-col] :is(input, select) { color: inherit; border-color: var(--border); background: var(--surface); appearance: auto; }
1219
1243
  }
1220
1244
 
1221
1245
  /* two-thirds / one-third columns on desktops (builder Users page) */
@@ -1948,9 +1972,25 @@ a.search-hit:hover, a.search-hit:focus-visible { background: var(--accent-soft);
1948
1972
  .th-menu-values li.muted { padding: 0.35rem 0.9rem; }
1949
1973
  .js th[data-col] > a { cursor: pointer; }
1950
1974
 
1951
- /* (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 */
1952
1976
  .date-field { position: relative; }
1953
- .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; }
1954
1994
 
1955
1995
  /* (sprint 77) display positions: banner, breadcrumb bar, a side column, footer; button positions in a region */
1956
1996
  .t-pos-banner, .t-pos-breadcrumb { margin-bottom: 1rem; }
@@ -1965,3 +2005,40 @@ a.search-hit:hover, a.search-hit:focus-visible { background: var(--accent-soft);
1965
2005
  @media (max-width: 900px) { .t-columns-left, .t-columns-right { grid-template-columns: 1fr; } .t-columns-right > .t-side { order: 0; } }
1966
2006
  .region-footer-split { display: flex; justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; }
1967
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); }