pgkiln 0.52.0 → 0.54.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 (68) hide show
  1. package/README.md +1 -1
  2. package/SECURITY.md +6 -1
  3. package/db/migrations/125_app_change.sql +23 -0
  4. package/db/migrations/126_mail_queue_network_access.sql +119 -0
  5. package/docs/apex-feature-parity.md +41 -12
  6. package/docs/guide/01-installation.md +1 -1
  7. package/docs/guide/03-builder.md +116 -10
  8. package/docs/guide/06-processing.md +6 -1
  9. package/docs/guide/08-security.md +55 -0
  10. package/docs/guide/09-reference.md +3 -1
  11. package/docs/guide/11-from-apex.md +1 -0
  12. package/docs/guide/12-development.md +52 -11
  13. package/docs/guide/14-globalization.md +49 -1
  14. package/docs/guide/18-cli.md +21 -1
  15. package/docs/guide/19-rest-data-sources.md +12 -0
  16. package/docs/guide/20-ai-agents.md +8 -2
  17. package/examples/gallery/README.md +37 -6
  18. package/examples/gallery/gallery.json +23 -1
  19. package/examples/gallery/samples/sample-calendar.sql +172 -0
  20. package/examples/gallery/samples/sample-cards.sql +157 -0
  21. package/examples/gallery/samples/sample-charts.sql +265 -0
  22. package/examples/gallery/samples/sample-dynamic-actions.sql +177 -0
  23. package/examples/gallery/samples/sample-grids.sql +151 -0
  24. package/examples/gallery/samples/sample-reporting.sql +151 -0
  25. package/examples/gallery/samples/ux-patterns.sql +295 -0
  26. package/examples/gallery/utilities/announcements.json +24 -0
  27. package/examples/gallery/utilities/lookup-lists.json +28 -0
  28. package/examples/gallery/utilities/release-notes.json +41 -0
  29. package/examples/hr/hr_70_data_security.sql +79 -0
  30. package/package.json +1 -1
  31. package/public/app.css +99 -23
  32. package/public/builder.css +71 -1
  33. package/public/builder.js +4 -0
  34. package/src/app.ts +9 -0
  35. package/src/builder/app-updates.ts +55 -0
  36. package/src/builder/data-security.ts +182 -0
  37. package/src/builder/gallery.ts +94 -47
  38. package/src/builder/home.ts +16 -5
  39. package/src/builder/mail-queue.ts +212 -0
  40. package/src/builder/mail.ts +72 -45
  41. package/src/builder/network-access.ts +140 -0
  42. package/src/builder/object-actions.ts +1 -0
  43. package/src/builder/object-browser.ts +1 -1
  44. package/src/builder/rest-catalogs.ts +19 -4
  45. package/src/builder/sql.ts +2 -0
  46. package/src/builder/themeroller.ts +47 -6
  47. package/src/builder/ui.ts +5 -0
  48. package/src/builder/websources.ts +1 -1
  49. package/src/builder/workspace-mail.ts +121 -0
  50. package/src/builder/workspaces.ts +1 -0
  51. package/src/cli/main.ts +25 -3
  52. package/src/cli/mcp.ts +40 -4
  53. package/src/cli/only.ts +52 -0
  54. package/src/cli/replace.ts +5 -1
  55. package/src/datasec.ts +343 -0
  56. package/src/mail/mail.ts +50 -11
  57. package/src/mail/providers.ts +6 -3
  58. package/src/metadata.ts +2 -0
  59. package/src/network-acl.ts +126 -0
  60. package/src/objectstore.ts +3 -0
  61. package/src/runtime/charts.ts +4 -1
  62. package/src/runtime/engine.ts +3 -1
  63. package/src/runtime/regions.ts +8 -5
  64. package/src/runtime/render.ts +4 -2
  65. package/src/runtime/styles.ts +54 -12
  66. package/src/webclient.ts +45 -4
  67. package/src/webrequests.ts +5 -0
  68. package/src/websources.ts +30 -12
@@ -781,7 +781,12 @@ Microsoft retires basic authentication (a password) for SMTP in Exchange Online:
781
781
  or the Graph provider. Secrets are stored encrypted with `PGKILN_SECRET_KEY`, are write-only in the
782
782
  builder, never exported and never readable by applications. Each service lists the **From
783
783
  addresses** it may send as (an address, or `@example.com` for a whole domain); an administrator
784
- chooses each application's service, its From address and an optional **limit per day**.
784
+ chooses each application's service, its From address and an optional **limit per day**. A
785
+ workspace's administrators can also set up the [workspace's own mail
786
+ service](03-builder.md#a-workspaces-own-mail-service) (sprint 118): the workspace's applications that
787
+ an administrator gave no service send through it, with its default From address. Administration →
788
+ [Mail queue](03-builder.md#mail-queue) shows the delivery per hour and per application, the slowest
789
+ provider calls and the failures with the provider's answer, with Retry and Purge.
785
790
 
786
791
  **How mail is sent.** `meta.send_mail` and the others only *queue* the mail, in the caller's
787
792
  transaction: a transaction that rolls back sends nothing. After the commit, the server sends it at
@@ -540,6 +540,61 @@ quoted with `escapeIdentifier`, every value is checked (dates, numbers, schema n
540
540
  The page is for administrators only (others get 403), every change needs the CSRF token, and pgkiln's own roles can't
541
541
  be changed, dropped or granted from it, so the page can't take pgkiln's own connections away.
542
542
 
543
+ ## Data security per table: rows, columns and values
544
+
545
+ (sprint 119) PostgreSQL has row level security but no column masking, so pgkiln puts a table's security in three
546
+ layers, all ordinary database objects, and shows them on one page: **SQL Workshop → Object Browser → a table →
547
+ Security** (also *Security…* in the table's ⋯ menu). It is pgkiln's answer to APEX 26.2's Deep Data Security.
548
+
549
+ - **Rows**: the table's row level security policies (above), listed with a link to Table Properties to switch row
550
+ level security on and to *Add policy*.
551
+ - **Columns**: a matrix of **database roles × columns** with *read* and *update* per cell. The rows are the roles
552
+ that have privileges on the table and the database roles of the workspace's applications (with the applications
553
+ that run as them). Apply writes `GRANT` / `REVOKE SELECT (c, …)` and `UPDATE (c, …)`. A column a role may not read
554
+ is refused by PostgreSQL ("permission denied") whoever sends the query. The table's owner, superusers and roles that
555
+ bypass row level security are listed apart: column privileges can't restrict them.
556
+ - **Values**: masked columns per **application role** (`meta.has_role()` of the signed-in user). A column's mask is
557
+ *empty (null)*, `***` or `***` plus the last four characters; tick the application roles that see the real value,
558
+ and *Everyone else* for users with none of the listed roles. The masks live in a **masking view** next to the table
559
+ (default name `<table>_masked`):
560
+
561
+ ```sql
562
+ create view sales.orders_masked with (security_barrier) as
563
+ select t.id, t.customer,
564
+ case when (select meta.has_role('manager')) then t.amount end as amount,
565
+ case when false then t.iban::text else '***' || right(t.iban::text, 4) end as iban
566
+ from sales.orders t;
567
+ ```
568
+
569
+ The view is owned by a role per schema, `<schema>_masking` (nologin), which pgkiln creates the first time. That
570
+ role is no owner, superuser or role that bypasses row level security (Apply checks it), so the table's policies
571
+ still apply inside the view; and it is a member of the database roles that read the table, so policies written
572
+ `to <role>` apply as well. The configuration is kept in the view's comment, so it travels with `pg_dump`,
573
+ Generate DDL and the change log; there is no `meta` table for it.
574
+
575
+ **Preview SQL** shows the statements, **Apply** runs them in one transaction (and records a change set in the
576
+ Change Log), **Open in the worksheet** puts them there. Nothing is changed when one statement fails.
577
+
578
+ **The mask holds only where the view is read.** As long as a role may still read the raw column on the table, its
579
+ queries on the table see the value; the preview warns about each such column. Point the application's pages,
580
+ REST handlers and AI tools at the view, then untick *read* of the masked columns for the role, and the value is
581
+ masked everywhere that role connects.
582
+
583
+ Every client meets the same rules because they all run as the application's database role with the same identity:
584
+
585
+ | Client | Runs as | `meta.app_user()` / `meta.has_role()` from |
586
+ |---|---|---|
587
+ | Pages, processes, dynamic actions, AI assistant and AI agent tools | the application's role (`appTx`) | the session |
588
+ | REST module handlers, AutoREST | the application's role | the bearer token (its user, the user's roles) |
589
+ | The MCP server's `run_query` with `alias` (and `user`) | the application's role | the user's roles in the application ([chapter 20](20-ai-agents.md#safety)) |
590
+ | psql, other tools | whatever role they log in as | — (only grants and policies apply) |
591
+
592
+ Policies and masks that use `meta.has_role()` can be combined: with a policy that shows a manager the rows of their
593
+ department, and `amount` masked for everyone but `manager`, a clerk reads their own rows with an empty amount.
594
+ Policies written per database role (`to sales_app`) apply to the masking role for every role it is a member of, so
595
+ prefer policies on `meta.app_user()` / `meta.has_role()` on tables with a masking view. The HR example's page 48
596
+ *Salaries (masked)* reads `hr.emp_masked` (`examples/hr/hr_70_data_security.sql`).
597
+
543
598
  ## Session state protection
544
599
 
545
600
  - **URL items**: on pages with protection *Arguments must have checksum* (the default), item
@@ -81,7 +81,9 @@ select meta.web_request_source('EXCHANGE', '{"currency": "EUR"}');
81
81
  must be on `PGKILN_REST_ALLOWED_HOSTS` (unset: no calls), private and loopback addresses are refused
82
82
  when the connection is made unless the host is in `PGKILN_REST_PRIVATE_HOSTS`, redirects (at most 3)
83
83
  go through the same checks, the response is cut off at `PGKILN_REST_MAX_BYTES` (default 5 MB) and
84
- the time limit is 1–60 seconds.
84
+ the time limit is 1–60 seconds. A workspace's [network access list](03-builder.md#network-access)
85
+ (sprint 118) narrows the hosts for its applications: a refused request ends with `status` `error`
86
+ and the reason in `message`, and is logged (debug log, Refused calls).
85
87
  - **Web credentials** are named, never passed: `p_credential => 'NAME'` signs the request with a
86
88
  credential of the current application (basic, bearer, header, OAuth2), and only for the URLs it is
87
89
  valid for. The secret is decrypted by the server for the request only and is never stored with it
@@ -65,6 +65,7 @@ What's missing is listed in the [feature parity matrix](../apex-feature-parity.m
65
65
  | Automatic Time Zone | Settings → Globalization → Time zone and Automatic time zone; My account → Time zone ([chapter 14](14-globalization.md#time-zones)) |
66
66
  | Universal Theme's default style (26.1: *Iris*) | Base style *Iris* (the default for new applications), *Lavender* or *Standard* ([chapter 14](14-globalization.md#base-styles-iris-lavender-and-standard)) |
67
67
  | Theme styles, *Enable End Users to Choose Theme Style* | Theme style (automatic/light/dark) and *Users may choose light or dark*; Theme Roller style variants with *Users may choose a style* ([chapter 14](14-globalization.md#style-variants-theme-roller)) |
68
+ | Universal Theme 26.2: *Pillar Light* / *Pillar Dark*, Theme Roller's *Header Pillar Strip*, `u-color-*-subtle` | Theme Roller → *Navigation appearance* and *Header strip colour* (per application or style), the same utility classes ([chapter 14](14-globalization.md#navigation-appearances-pillar-light-and-pillar-dark)) |
68
69
  | Template Options (regions, buttons) | *Template options* in the Page Designer: CSS classes from a fixed list ([chapter 4](04-pages-and-regions.md#template-options)) |
69
70
  | ORDS | Not needed to serve apps; REST APIs with PostgREST, see [below](#ords-and-postgrest) |
70
71
  | Export `f123.sql` / APEXlang | `meta.export_app('alias')` (JSON) |
@@ -10,6 +10,7 @@ db/
10
10
  seed/*.sql the HR sample (not for production)
11
11
  examples/ example applications as SQL (the tutorial)
12
12
  examples/plugins/ plug-in files (template components) to import
13
+ examples/gallery/ the App Gallery: gallery.json, starters/ and utilities/ (blueprints), samples/ (single-file sample apps)
13
14
  scripts/migrate.ts migration/seed runner (src/migrate.ts does the work)
14
15
  website/ the website pgkiln.vargar.eu (VitePress): landing page + this guide, synced from docs/ at build time;
15
16
  cd website && npm install && npm run dev; scripts/deploy.sh publishes it (see website/deploy/)
@@ -42,7 +43,8 @@ src/
42
43
  merge into the main application or refresh the copy, both through cli/replace.ts
43
44
  cli/ the command line: main.ts (commands, help, exit codes), apps.ts (connect, export,
44
45
  read, import), files.ts (directories, zip), diff.ts, replace.ts (import --replace in place),
45
- mcp.ts (`pgkiln mcp`: the MCP server for AI coding agents, chapter 20)
46
+ mcp.ts (`pgkiln mcp`: the MCP server for AI coding agents, chapter 20), only.ts (sprint 116:
47
+ `import --replace --only pages/12`, the named files merged over the application's current export)
46
48
  app.ts / server.ts Fastify setup / entry point
47
49
  db.ts the owner and shared runtime pools, `runtime` (the current scope's: runtimeScope), appTx() (SET LOCAL ROLE + pgkiln.* settings, NOTICEs to the debug log), savepoints
48
50
  workspace-db.ts isolated workspaces (090): the isolated applications (cached 5 s), a pool per workspace login (passwords from
@@ -95,6 +97,9 @@ src/
95
97
  objects, type reporting, roles viewer/editor/administrator as schemes EDITOR and ADMIN, page 1 a Data
96
98
  Reporter), reportingApps(), reportingAppCount() (meta.reporting_app_count, for the workspace home)
97
99
  tabledef.ts (sprint 95) a table as a definition: readTable(), checkTable(), tableSql() (CREATE, or ALTER from the catalog's version)
100
+ datasec.ts (sprint 119, WS-13) data security per table: readSecurity() (policies, column privileges per role, the
101
+ masking view and its configuration in the view comment), checkSecurity(), securitySql() (GRANT / REVOKE,
102
+ the masking view owned by <schema>_masking); the page is builder/data-security.ts
98
103
  sqlformat.ts (sprint 93) formatSql(): the worksheet's Format (clauses per line, keyword case; strings and comments kept)
99
104
  sqlplan.ts (sprint 92) EXPLAIN (FORMAT JSON) as a tree: parsePlan() (self time, slowest step, misestimates), planText()
100
105
  quicksql.ts Quick SQL: shorthand parser and PostgreSQL DDL generator (unit tested)
@@ -110,7 +115,12 @@ src/
110
115
  geocode.ts the installation's geocoder for address items (GEOCODER_URL, Nominatim search API)
111
116
  mvt.ts Mapbox Vector Tile encoding for map layers served as tiles
112
117
  yamltext.ts the text style of the directory export: a strict YAML subset, written and read
113
- webclient.ts outgoing HTTP to web services: allow-list, address checks at connect time (SSRF), redirects, limits
118
+ webclient.ts outgoing HTTP to web services: allow-list, address checks at connect time (SSRF), redirects, limits;
119
+ (sprint 118) a request's scope (OutboundScope): the workspace's network access list, checked on every hop
120
+ (checkUrl/scopeProblem), refusals reported to the scope
121
+ network-acl.ts (sprint 118, WS-9) workspace network access lists (meta.workspace.network_*): scopeForApp/scopeForWorkspace
122
+ (no cache), parseHostList, recordRefusal/purgeRefusals (meta.network_refusal, 7 days), mailHostAddress
123
+ (a workspace mail service's SMTP host: both lists, resolved once, the checked address connected to)
114
124
  datapump.ts (sprint 103, DA-16) Schema Export / Import jobs (meta.dump_job): findTools (pg_dump/pg_restore ≥ the
115
125
  server's major), startExport (pg_dump, argument array, PGPASSWORD), readToc/parseToc (pg_restore --list),
116
126
  startImport (pg_restore -f - run in one transaction between schema renames; data-only in FK order),
@@ -137,7 +147,10 @@ src/
137
147
  or XOAUTH2, Microsoft Graph sendMail, Amazon SES v2 raw MIME with signV4, Resend, Postmark, SendGrid,
138
148
  Mailgun, log; MailError says whether a retry may help; client-credentials token cache; settingsProblems),
139
149
  mail.ts (sendWith(): service enabled, allowed sender, secret decrypted; mailTick(): claim with SKIP LOCKED,
140
- send, retry after 1/4/16/64 minutes, purge after 30 days; the NOTIFY pgkiln_mail listener)
150
+ send, retry after 1/4/16/64 minutes, purge after 30 days; the NOTIFY pgkiln_mail listener);
151
+ (sprint 118) the service of meta.mail_service_for_app (the app's, else its workspace's own), each attempt's
152
+ provider and duration, serviceSettings()/verifyService(): a workspace service's checks (no base or token URL,
153
+ its SMTP host through network-acl.ts mailHostAddress)
141
154
  ai/ AI services (migration 060): types.ts (the provider interface, AiError kinds),
142
155
  anthropic.ts (Claude through @anthropic-ai/sdk: streamed, effort, structured outputs via
143
156
  output_config.format, refusal fallbacks, stop_reason checks, typed SDK errors), openai.ts (the openai
@@ -164,7 +177,8 @@ src/
164
177
  shortcuts.ts (103) "NAME" shortcuts in static content, help texts and confirm messages
165
178
  render.ts page chrome (nav, breadcrumb), dynamic action JSON, theme (the page's nonce'd <style>, light/dark and style switches)
166
179
  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
167
- style (user choice, default), themeCss() (only hex values and constants reach the CSS)
180
+ style (user choice, default), themeCss() (only hex values and constants reach the CSS); (sprint 117) NAV_APPEARANCES
181
+ (Pillar Light / Dark), navAppearance() → the <body> classes nav-pillar-*, the header strip (--strip)
168
182
  template-options.ts template options: the fixed CSS class list per region and button, templateClasses() (unknown values ignored)
169
183
  regions.ts region shell + chart (drill-down links, gauge settings)/cards/dynamic dispatch with row limits, lazy placeholder and cache, buttons (menu buttons, badges; (124) menu entries {action} → data-menu-entry for menu_entry dynamic actions)
170
184
  report.ts, report-views.ts (group by, pivot, chart), compute.ts (computed column expressions), grid.ts (aggregates, row actions, Actions menu, single row view, layoutFromForm), grid-layout.ts (column layouts: clean, arrange, per user), master-detail.ts (signed master row selection, details), facets.ts (facetChart: a facet's counts as a chart, sprint 65), items.ts
@@ -253,7 +267,10 @@ src/
253
267
  wizards.ts create page wizards: step 2 forms per page type (defaults from meta.wizard_defaults), POST → meta.generate_page
254
268
  (the generators are PL/pgSQL in migration 047: catalog, defaults, form/cards/calendar/chart/map/facets/master-detail)
255
269
  home.ts App Builder home at /builder/apps and /builder (tiles, applications report/cards, Recent; /builder redirects
256
- to the Workspace home when meta.workspace.builder_home = 'home'), Create, Import, Dashboard, Utilities
270
+ to the Workspace home when meta.workspace.builder_home = 'home'), Create, Import, Dashboard, Utilities;
271
+ (116) Updated by, working copies and the application lock in the report and the cards
272
+ app-updates.ts (sprint 116, migration 125) who changed an application last: developer() notes a builder POST under
273
+ /apps/:id or /pages/:pid (or a Create / Import), an onSend hook records a success in meta.app_change
257
274
  workspace-home.ts (sprint 109, migration 119) the Workspace home /builder/home, opened after sign-in: tool cards (Data Reporter
258
275
  when GET /builder/reporter exists), workspace message edited in place (workspaceAdmin), Summary, Top
259
276
  applications / users / developers over meta.developer.home_timeframe, System message (src/release-check.ts),
@@ -286,7 +303,15 @@ src/
286
303
  (Workspace home). Every id from a request is looked up in the current workspace
287
304
  region-help.ts the Attributes (JSON) help per region type, with links to the guide
288
305
  mail.ts Workspace utilities → Mail services (administrators: providers, write-only secret, allowed senders,
289
- applications with From and daily limit, test) and Applications → Activity → Mail (retry, cancel)
306
+ applications with From and daily limit, test) and Applications → Activity → Mail (retry, cancel);
307
+ serviceForm/formValues/runCheck shared with workspace-mail.ts (sprint 118)
308
+ mail-queue.ts (sprint 118, WS-9) Administration → Mail queue (/builder/mail-queue): queueScope (administrators: all or
309
+ ?ws=; workspace administrators: the current workspace), figures, per hour, per workspace / application,
310
+ slowest provider calls, failures; Retry / Purge one or all failed, scope checked again per mail
311
+ network-access.ts (sprint 118, WS-9) Administration → Network access (/builder/network): the installation's list, a workspace's
312
+ list (workspaceAdmin), Check a URL (nothing called), the workspaces (administrators), refused calls
313
+ workspace-mail.ts (sprint 118, WS-9) a workspace's own mail service (/builder/workspaces/:id/mail, WORKSPACE_<id>): its
314
+ administrators only, write-only secret, checks through mail.ts runCheck with the workspace's guard
290
315
  shared.ts Shared Components and access control
291
316
  designer.ts page designer: component tree (with computations and branches), layout canvas and gallery, property editor, toolbar;
292
317
  (sprint 86) GET /builder/apps/:app/pages/:no, /builder/pages/:id redirects browsers there (designerUrl in ui.ts)
@@ -304,6 +329,7 @@ src/
304
329
  object-browser.ts (sprint 75) the Object Browser: objects by type, system objects hidden, a table in tabs, column actions as SQL to review
305
330
  table-editor.ts (sprint 95) Table Properties: GET/POST /sql/objects/table (create, edit ?o=, ?template=), fromForm(), Preview / Apply
306
331
  (one transaction) / Open in the worksheet
332
+ data-security.ts (sprint 119) a table's Security: GET/POST /sql/objects/security?o= (policies, roles × columns, masks), Preview / Apply
307
333
  object-actions.ts (sprint 94) an object's ⋯ menu (objectMenu) in the worksheet and the Object Browser: Quick DDL (objectDdl), Drop with
308
334
  dependents, Add index / trigger / policy as drafts; GET /sql/objects/action, /sql/objects/panel (Open in a panel)
309
335
  data-modeler.ts (sprint 98) SQL Workshop → Data Modeler: /sql/modeler (diagrams, meta.sql_diagram, 111), /:id (SVG), /:id/objects, /layout,
@@ -348,7 +374,8 @@ src/
348
374
  ldap.ts Users → LDAP directories
349
375
  documents.ts document template preview (Shared Components)
350
376
  pwa.ts Settings → Progressive Web App (icon upload)
351
- themeroller.ts Settings → Theme Roller: style variants (add, edit, rename, delete), default style, users may choose
377
+ themeroller.ts Settings → Theme Roller: style variants (add, edit, rename, delete), default style, users may choose;
378
+ (sprint 117) navigation appearance and header strip colours (application and per style), the preview's menu
352
379
  subscriptions.ts Shared Components → Subscriptions: subscribe, refresh, unsubscribe, subscribers and publish; the note under a component
353
380
  reporter.ts page designer: Data Reporter settings (sources: table or view, offered columns, labels, masks; sharing;
354
381
  (sprint 110) a dataset as a source, who may edit, who may moderate, publishing)
@@ -374,8 +401,10 @@ src/
374
401
  templates.ts template components: preview, plug-in export/import, region settings, report column templates
375
402
  code-editor.ts code fields (data-code marks), /builder/code/completions (scoped to the app's role), /builder/code/check
376
403
  instance.ts Workspace utilities → Instance settings (src/instance.ts: session and sign-in settings, configuration overview)
377
- gallery.ts the App Gallery (sprint 61): examples/gallery/gallery.json, sample apps (administrators, migrate --example),
378
- starter apps (blueprints → openBlueprint's review), custom apps (the workspace's boilerplates)
404
+ gallery.ts the App Gallery (sprint 61, 120): examples/gallery/gallery.json, sample apps (administrators: migrate --example,
405
+ or one SQL file of examples/ or examples/gallery/samples/ in one transaction), starter and utility apps
406
+ (blueprints of starters/ and utilities/ → openBlueprint's review), custom apps (the workspace's boilerplates);
407
+ four tabs with counts, ?q= search
379
408
  app-groups.ts application groups (092): a workspace's groups, a group per application (the App Builder home filters)
380
409
  cross-app.ts Cross Application Reports (sprint 61): REPORTS (one SQL per topic over the workspace's applications)
381
410
  workspaces.ts workspaces (064): loadWorkspaces/appAllowed (checked in ui.ts developer() for every /apps/:id and /pages/:pid
@@ -396,7 +425,8 @@ src/
396
425
  into agents and regions), toolCallTable() (Activity → AI usage → Tool calls), agentOptions()
397
426
  supporting.ts supporting objects: review page, running the install/upgrade/deinstall scripts as the app's role in one transaction
398
427
  public/
399
- app.css theme (light/dark, responsive; --font, --font-size, --radius for style variants; template option classes to-*)
428
+ app.css theme (light/dark, responsive; --font, --font-size, --radius for style variants; template option classes to-*;
429
+ (sprint 117) navigation tree states, RTL via logical properties, body.nav-pillar-*, u-color-*-subtle, the busy bar)
400
430
  app.js client runtime: dialogs (dialog_closed actions), popup LOVs (sprint 115: the next page loads on scroll through an
401
431
  IntersectionObserver, ‹/More without one), dynamic actions (focus, classes, messages; sprint 115: wait_popup /
402
432
  remove_wait_popup as a modal <dialog class="wait-popup">, pgkiln.waitPopup(), the menu_entry event), 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)
@@ -455,6 +485,8 @@ test/
455
485
  page-logic.test.ts download, chain (background jobs) and workflow processes, function/app branches, dialog_closed (HR page 28)
456
486
  code-editor.test.ts code editor: completions scoped to the app's role, the check, marked fields
457
487
  builder-home.test.ts App Builder home: search, sort, views, Recent, Create/Import pages, dashboard, utilities
488
+ app-list.test.ts (116) the App Builder list's Updated by, working copies and lock; what counts as a change
489
+ import-only.test.ts (116) pgkiln import --replace --only: a page or component file, refusals
458
490
  charts.test.ts chart markup per kind (geometry as classes), gauges, Gantt time axis and dependencies, pyramid, polar, range, box plot, stock, drill-down links
459
491
  calendar.test.ts calendar views, create links, moving events (pure and over HTTP)
460
492
  rest-sources.test.ts REST data sources, web credentials, SSRF checks, invoke_api (mock service + HR page 23)
@@ -473,8 +505,14 @@ test/
473
505
  builder-parity.test.ts lists (HR page 31), page and application locks, comments, developers, supporting objects
474
506
  page-wizards.test.ts create page wizards: catalog defaults, every page type generated and rendered, refusals, the builder steps
475
507
  theme-styles.test.ts Theme Roller style variants (checks, CSS, user choice per app, builder page), template options, base style Iris
508
+ theme-pillar.test.ts (sprint 117) Pillar Light / Dark appearances and the header strip: checks, CSS, runtime classes, Theme Roller, export
476
509
  workspaces.test.ts workspaces (064): Default, administrators' pages, current workspace, refused apps, imports and copies
477
- gallery.test.ts the App Gallery: every starter created from its review with all its pages rendering; sample apps (admins)
510
+ mail-network.test.ts (sprint 118) network access lists (page process refused and logged in debug, narrowing only, redirects,
511
+ the page), a workspace's mail service (sending, SMTP host on the list), the Mail queue (yesterday's
512
+ SMTP failure, Retry, Purge, counts, scope)
513
+ gallery.test.ts the App Gallery: every starter and utility created from its review and every sample app installed, with all
514
+ their pages rendering; sample apps (admins), the search, a failing script leaves nothing
515
+ gallery-samples.ts the gallery's shipped apps for the tests: SAMPLES (schema and role of each script), BLUEPRINTS, drop/install
478
516
  builder-apex-screens.test.ts sprint 61: sign-in with a workspace, message and banner, utilization, SQL Workshop home, groups, reports
479
517
  workspace-isolation.test.ts isolated workspaces (090): logins, RLS on meta, app_id, roles, SQL Workshop, scripts, imports
480
518
  ai.test.ts AI services: Generate text with AI (text, structured outputs, errors, limits, keys), its dynamic action,
@@ -493,6 +531,9 @@ test/
493
531
  ai-mock.ts a local mock of the Claude Messages API (SSE stream) and OpenAI Chat Completions
494
532
  helpers.ts a cookie-keeping test browser
495
533
  e2e/responsive.test.ts browser tests at phone/tablet/desktop widths (Playwright)
534
+ e2e/gallery.test.ts (sprint 120) the gallery's sample apps: every page at 4 widths, CSP, axe; dynamic actions; the four tabs at 390 px
535
+ e2e/admin-mail-network.test.ts (sprint 118) Mail queue, Network access and a workspace's mail service at 390 px without JavaScript
536
+ e2e/pillar.test.ts (sprint 117) Pillar appearances: axe in light and dark, subtle colours, current vs parent entry, Arabic RTL, 390 px, preview
496
537
  e2e/code-editor.test.ts the code editor in a browser: highlighting, keys, suggestions, touch, screen readers, syntax colours at AA in both themes (sprint 108)
497
538
  e2e/data-modeler.test.ts (sprint 98) the Data Modeler in a browser: drag, save, reload, undo/redo, zoom, mini-map, exports, phone
498
539
  e2e/worksheet.test.ts (sprint 91) the SQL Worksheet in a browser: run keys, tabs, navigator, drag and drop, splitters, phone, no JavaScript;
@@ -245,6 +245,8 @@ application. The base style and colours under Settings → Theme are the *Standa
245
245
  |---|---|
246
246
  | Accent colour, header colour | `#rrggbb` (or "as the base"), for the light theme |
247
247
  | Accent and header colour in dark mode | `#rrggbb` (or the base style's dark palette), for the dark theme; choose light colours (text on the accent is dark there) |
248
+ | Navigation appearance | *As the application*, *Default* (the base style's header and menu), *Pillar Light* or *Pillar Dark*, see [below](#navigation-appearances-pillar-light-and-pillar-dark) |
249
+ | Header strip colour, also for dark mode | `#rrggbb` or an item reference (else the application's strip, else the accent) |
248
250
  | Font | *System* (default), *Humanist sans*, *Geometric sans*, *Serif*, *Rounded*, *Monospace* (font stacks of fonts on the device; nothing is downloaded) |
249
251
  | Font size | 14, 15 (default), 16 or 17 px |
250
252
  | Corners | Square, 4, 8 (default) or 14 px, for regions, buttons and fields |
@@ -276,10 +278,56 @@ per tenant, environment or user group:
276
278
  a style themselves get the first style (in the list's order) whose condition holds, else the
277
279
  default style. The condition runs as the application's database role in the page's transaction;
278
280
  an error counts as *false* and is written to the [debug messages](06-processing.md#debug-messages).
279
- - **Dynamic colours.** Instead of a picked colour, any of a style's four colours may be an item
281
+ - **Dynamic colours.** Instead of a picked colour, any of a style's colours may be an item
280
282
  reference such as `&APP_BRAND_COLOUR.`: the item's value in session state (set by a computation,
281
283
  an application process after login, a form…), used when it is a `#rrggbb` colour and ignored
282
284
  otherwise, so a stored value can never become anything but a colour in the CSS.
283
285
 
284
286
  Together: one style *Tenant brand* with condition `:APP_BRAND_COLOUR is not null` and accent
285
287
  `&APP_BRAND_COLOUR.` gives each tenant its colour from a table, read once after sign-in.
288
+
289
+ ## Navigation appearances: Pillar Light and Pillar Dark
290
+
291
+ APEX 26.2's Universal Theme adds *Pillar* appearances for the header and the navigation menu. In pgkiln:
292
+ **Theme Roller → Navigation appearance** (for the whole application) or a style's own *Navigation appearance*:
293
+
294
+ | Appearance | Look |
295
+ |---|---|
296
+ | **Default** | The base style's header and menu (Iris: a dark header and menu; Standard: a navy header and a white menu) |
297
+ | **Pillar Light** | Header and menu on the page's own light surfaces (in dark mode its dark ones), a line under the header |
298
+ | **Pillar Dark** | Header and menu as one dark column in the header colour; in dark mode a darker one |
299
+
300
+ Both have a **header pillar strip** across the top of the header, in the *Header strip colour* (light and dark mode,
301
+ on the Theme Roller page or per style; a style may take it from an item, like its other colours), else the accent,
302
+ and show the **current page as a filled pill** in the accent. Pillar Light replaces the header colour with the page's
303
+ surface, so the strip carries the brand colour. A style's appearance wins over the application's; *Default* in a style
304
+ switches a pillar appearance off. The appearance works with a side and a top menu, and on tablets and phones in the
305
+ drawer. It is stored as `"appearance"`, `"strip"` and `"strip_dark"` in `meta.app.theme` (and in each style), so it
306
+ travels with export and import; only the two keys of the list and `#rrggbb` colours reach the page (classes
307
+ `nav-pillar nav-pillar-light` / `nav-pillar-dark` on `<body>`, `--strip` in the nonce'd `<style>`).
308
+
309
+ ### The navigation tree
310
+
311
+ In every appearance (sprint 117) the menu tells three states apart: the **current page** is filled, in the accent,
312
+ with a bar at the start of the entry (a pill in the pillar appearances); its **parent entries** (the trail) are bold
313
+ with an accent icon but not filled, and the open parent's sub-menu hangs on an accent guide line; the **selected**
314
+ entry (keyboard focus) has a ring inside the menu. A page that is not in the menu marks its nearest menu ancestor as
315
+ the trail.
316
+
317
+ ### Right to left
318
+
319
+ Arabic, Hebrew, Persian and Urdu pages (`dir="rtl"`) mirror the chrome: the menu sits on the right, the drawer
320
+ slides in from the right on tablets and phones, the current page's bar, the guide lines and the menu's chevrons turn
321
+ around, menus open from the other side and reflowed report cards are aligned to the start.
322
+
323
+ ### Subtle colour utilities and the processing indicator
324
+
325
+ The classes `u-color-1-subtle` … `u-color-15-subtle` and `u-color-success-subtle`, `u-color-danger-subtle`,
326
+ `u-color-warning-subtle`, `u-color-info-subtle`, `u-color-neutral-subtle` give an element a tinted background with
327
+ readable text (WCAG AA in light and dark mode, in every base style), for static content, HTML expressions and
328
+ template components, e.g. `<span class="u-color-4-subtle">Approved</span>`.
329
+
330
+ A region that is busy (a dynamic action, a refresh) is dimmed slightly after 150 ms, so quick answers don't flicker,
331
+ and shows a thin moving bar along its top (a still bar when the device asks for reduced motion); the wait popup's
332
+ spinner is lighter too.
333
+
@@ -35,7 +35,7 @@ any (see [what is not exported](03-builder.md#export-format)).
35
35
  | `pgkiln migrate [--example <name>]` | Applies the migrations that were not applied yet, then optionally `examples/<name>/` (same as `npm run db:migrate` / `npm run example:hr`; `--root` and `--seed` as in `scripts/migrate.ts`) |
36
36
  | `pgkiln apps [--json]` | Lists the applications: alias, number of pages, name |
37
37
  | `pgkiln export <alias> [--format json\|dir\|text] [--out <path>]` | `json` (default): the `pgkiln/2` document with sorted keys, to `--out` or standard output. `dir`: a directory (default `./<alias>`), see below. `text`: the same directory in YAML with the code inline ([text files](#text-files-apexlang)) |
38
- | `pgkiln import <path> [--alias <alias>] [--replace] [--check [--force] \| --dry-run]` | Imports a JSON export, an application directory or a `.zip` of one. `--alias` gives the copy another alias. `--replace` updates the application with that alias in place (after a [backup](03-builder.md#backups)). `--check` checks the application before it commits, `--dry-run` only checks ([below](#checking-before-an-import)) |
38
+ | `pgkiln import <path> [--alias <alias>] [--replace [--only <file>]…] [--check [--force] \| --dry-run]` | Imports a JSON export, an application directory or a `.zip` of one. `--alias` gives the copy another alias. `--replace` updates the application with that alias in place (after a [backup](03-builder.md#backups)); with `--only pages/12` (repeatable) only that page or component file ([below](#importing-one-page-or-component)). `--check` checks the application before it commits, `--dry-run` only checks ([below](#checking-before-an-import)) |
39
39
  | `pgkiln diff <alias> <path> [--name-only \| --quiet]` | What differs between the application in the database and a directory (or JSON file, or zip) |
40
40
  | `pgkiln ddl <schema> [--no-comments] [--no-grants] [--out <file>]` | The DDL of a schema's objects, in an order that runs (as SQL Workshop → DDL, [chapter 3](03-builder.md#sql-workshop)) |
41
41
  | `pgkiln compare <source> <target> [--script]` | Compares two schemas: `+` only in the source, `-` only in the target, `~` different; exit code 1 when they differ. `--script` writes the script that makes the target like the source (drops commented out); nothing is run |
@@ -210,6 +210,26 @@ pgkiln diff hr apps/hr # on the target: what would cha
210
210
  pgkiln import apps/hr --replace # deploy
211
211
  ```
212
212
 
213
+ ### Importing one page or component
214
+
215
+ (sprint 116; APEX 26.2: APEXlang file-level import) With `--replace`, `--only` imports just the named files of a
216
+ directory export (or a .zip of one) and leaves the rest of the application as it is in the database:
217
+
218
+ ```sh
219
+ pgkiln import apps/hr --replace --only pages/12 # page 12 with its regions, items, buttons …
220
+ pgkiln import apps/hr --replace --only shared/lovs/jobs.yaml # one shared component
221
+ pgkiln import apps/hr --replace --only pages/12 --only shared/lovs # several: repeat --only
222
+ ```
223
+
224
+ `pages/12` (or just `12`) is page 12's folder whatever its name, so a renamed page still matches; any other value is
225
+ a file or folder path inside the export. A page or file that the directory no longer has is removed from the
226
+ application. How it works: pgkiln exports the application in the directory's style, takes the named files from the
227
+ directory, and imports the result with `--replace` as usual: one transaction, a backup first, `--check` and
228
+ `--dry-run` work the same, and what belongs to the installation is kept. It says how many of the named files differ
229
+ (on standard error). `--only` refuses without `--replace`, for a JSON file, for a path outside the export and for a
230
+ selection that matches nothing in the directory or the application (exit code 2). A component that another file
231
+ refers to by name (a list of values used on a page, say) has to exist after the import, as with any import.
232
+
213
233
  ### Checking before an import
214
234
 
215
235
  An import doesn't need the application's tables, columns and functions to exist (neither does APEX's):
@@ -53,6 +53,16 @@ The checks on every outgoing request (`src/webclient.ts`), against server-side r
53
53
  Keep the allow-list as short as you can. `*` lets developers call any public host from the
54
54
  server; it never opens private addresses.
55
55
 
56
+ **A workspace's network access list** (sprint 118; APEX 26.2 workspace network ACLs) narrows this
57
+ list for one workspace: under Administration → [Network access](03-builder.md#network-access), its
58
+ administrators can limit its applications to some hosts (same entry syntax, without `*`). A call made
59
+ for an application of that workspace must then match the workspace's list *and* the installation's,
60
+ on every redirect hop; the address checks above stay with `PGKILN_REST_PRIVATE_HOSTS` alone, so a
61
+ workspace can never widen what the installation allows. It applies to every path above: REST data
62
+ sources (including write-back, synchronisation and the response cache), `meta.web_request`, invoke
63
+ API, token endpoints, object storage and REST source catalogs. Refusals are logged in the
64
+ application's debug log and listed under *Refused calls*.
65
+
56
66
  **Changing `PGKILN_SECRET_KEY`** makes the stored secrets unreadable: pgkiln then reports that
57
67
  the secret must be entered again. Keep the key with your other server secrets, outside the
58
68
  database: a database dump alone does not reveal the secrets.
@@ -323,6 +333,8 @@ values, processes and workflow steps refer to sources and credentials by name, s
323
333
 
324
334
  - Outgoing calls are off until the administrator sets `PGKILN_REST_ALLOWED_HOSTS`; private and
325
335
  internal addresses need `PGKILN_REST_PRIVATE_HOSTS` as well.
336
+ - A workspace's [network access list](03-builder.md#network-access) narrows the allow-list for its
337
+ applications (it can't widen it).
326
338
  - Secrets are encrypted with a key outside the database, write-only in the builder, excluded from
327
339
  exports and from the runtime role, and never logged. Use **Valid for URLs** so a credential is
328
340
  only ever sent to its own service.
@@ -52,7 +52,7 @@ directory the agent started the server in (the project).
52
52
  | `describe_schema` | schemas with tables, views and functions; or one table with columns, constraints, indexes, RLS policies and triggers | no |
53
53
  | `generate_ddl` | the DDL of a schema's objects, in an order that runs: a start for a migration script | no |
54
54
  | `compare_schemas` | the differences between two schemas and a script that makes the target like the source (not run) | no |
55
- | `run_query` | one SQL statement, read-only, rows as JSON | no |
55
+ | `run_query` | one SQL statement, read-only, rows as JSON; with `alias` (and `user`) as that application's role and user | no |
56
56
  | `search_docs` | searches this user guide, the parity matrix and SECURITY.md | no |
57
57
  | `recent_errors` | the latest errors and refused requests from the activity log | no |
58
58
  | `export_app` | writes an application to a directory (default `apps/<alias>`, format `text`) | files on disk |
@@ -85,7 +85,13 @@ migration scripts, not in the application files (`run_query` can't change them a
85
85
  columns whose names contain *password*, *secret*, *token*, *hash* or *api_key* are shown as
86
86
  `(hidden)`. That keeps credentials out of the agent's context by accident; it is not an access
87
87
  control: the server runs as the owner role, like the command line, and is meant for a
88
- developer's own machine. Don't point it at production; deploy with `pgkiln import --replace`
88
+ developer's own machine.
89
+ - **As an application's user.** (sprint 119) `run_query` with `alias` runs the statement as that application's
90
+ database role, with `meta.app_id()`, `meta.app_user()` (`user`, default `nobody`) and `meta.has_role()` (the
91
+ user's roles in the application) set, so grants, column privileges, row level security and masking views apply as
92
+ on that user's pages ([chapter 8](08-security.md#data-security-per-table-rows-columns-and-values)). Use it to check
93
+ what a user sees, or let an agent look at data the way the application does. It is no sandbox either: the
94
+ connection is still the owner's. Don't point it at production; deploy with `pgkiln import --replace`
89
95
  from git instead.
90
96
  - **Writes** are only `export_app` (files; it refuses a non-empty directory that isn't an export,
91
97
  so it can't overwrite your project) and `import_app` (the database, in one transaction). Both are
@@ -1,13 +1,44 @@
1
1
  # App Gallery
2
2
 
3
- The builder's **App Gallery** (`/builder/gallery`) lists what `gallery.json` names:
3
+ The builder's **App Gallery** (`/builder/gallery`) lists what `gallery.json` names, in four tabs:
4
4
 
5
5
  - `samples`: applications shipped with pgkiln, installed by an administrator: `example` names a directory of
6
- `examples/` (installed like `pgkiln migrate --example <name>`), `file` a single SQL file in `examples/`.
7
- `alias` is the application they create (an installed one shows Run and Edit).
6
+ `examples/` (installed like `pgkiln migrate --example <name>`), `file` a single SQL file, in `examples/` (the
7
+ tutorial, `tasks-app.sql`) or in `samples/` here (`samples/sample-charts.sql`). A file runs in one transaction as the
8
+ owner, so a failing script leaves nothing behind. `alias` is the application it creates (an installed one shows Run
9
+ and Edit).
8
10
  - `starters`: [blueprints](../../docs/guide/03-builder.md#creating-an-application-from-a-blueprint) in `starters/`.
9
11
  Install opens the blueprint's review; the developer creates the application from it.
12
+ - `utilities`: blueprints in `utilities/` of applications a workspace uses (lookup lists, release notes,
13
+ announcements), installed like starters.
14
+ - Custom apps are not listed here: they are the workspace's boilerplate applications.
10
15
 
11
- Add a starter by writing a blueprint file in `starters/` and an entry in `gallery.json` (a key of lower-case
12
- letters, digits and `-`, a title, an [icon name](../../docs/guide/09-reference.md#icons) and a description).
13
- `test/gallery.test.ts` creates every starter and opens all its pages.
16
+ ## The sample apps in `samples/`
17
+
18
+ | File | Application | Shows |
19
+ |---|---|---|
20
+ | `sample-charts.sql` | Sample Charts (`/a/sample-charts`) | the 22 chart types, a page per question, a drill-down to a report |
21
+ | `sample-cards.sql` | Sample Cards | cards with a form, KPI tiles, lists as cards, built-in template components |
22
+ | `sample-calendar.sql` | Sample Calendar | month, week, day and list views, create on click, drag and drop through a function |
23
+ | `sample-grids.sql` | Sample Interactive Grids | an editable grid, select lists, totals, frozen column, row menu, master-detail, views |
24
+ | `sample-dynamic-actions.sql` | Sample Dynamic Actions | show/hide/enable, set value, refresh, messages, wait popup, classes, inline dialog |
25
+ | `sample-reporting.sql` | Sample Reporting | interactive report, faceted search, smart filters, a classic report with column options |
26
+ | `ux-patterns.sql` | UX Pattern Catalog | region templates and options, buttons, items and form layouts, content and page patterns |
27
+
28
+ Each script creates its own schema (`sample_charts`, …), a role (`app_sample_charts`, …) granted to the runtime, the
29
+ application and one account, **demo** / **demo**. Without the builder:
30
+ `psql "$DATABASE_URL" -1 -f examples/gallery/samples/sample-charts.sql`. To remove one, delete the application in the
31
+ App Builder, then `drop schema sample_charts cascade; drop role app_sample_charts;`.
32
+
33
+ A sample script follows the pattern of the HR example: plain SQL that inserts `meta.*` rows, using
34
+ `meta.generate_page()` for wizard-made pages and two temporary helpers (`pg_temp.page_id`, `pg_temp.region_id`).
35
+ Every page starts with a text that says how it is made.
36
+
37
+ ## Adding an entry
38
+
39
+ Add a starter or utility by writing a blueprint file in `starters/` or `utilities/` and an entry in `gallery.json` (a
40
+ key of lower-case letters, digits and `-`, a title, an [icon name](../../docs/guide/09-reference.md#icons) and a
41
+ description). Add a sample by writing a script in `samples/` (no `begin`/`commit`: the gallery runs it in a
42
+ transaction) and an entry with its `alias` and `file`. `test/gallery.test.ts` creates every starter and utility,
43
+ installs every sample and opens all their pages; `test/e2e/gallery.test.ts` checks the samples' pages at four widths,
44
+ for CSP violations and with axe-core.
@@ -3,7 +3,21 @@
3
3
  {"key": "hr", "title": "HR Demo", "alias": "hr", "icon": "briefcase", "example": "hr",
4
4
  "description": "The showcase of pgkiln on the classic EMP and DEPT tables: reports and forms, an interactive grid, charts, maps, a calendar, faceted search, approvals and workflows, documents, a REST API, translations and a Progressive Web App. Demo accounts king, blake, jones, allen, scott and demo, with the username as password."},
5
5
  {"key": "tasks", "title": "Tasks", "alias": "tasks", "icon": "check", "file": "tasks-app.sql",
6
- "description": "The tutorial application: a personal task list where every user sees only their own tasks (row level security), with a business rule in PL/pgSQL behind a button. Demo accounts ann and bob (password ann-password, bob-password)."}
6
+ "description": "The tutorial application: a personal task list where every user sees only their own tasks (row level security), with a business rule in PL/pgSQL behind a button. Demo accounts ann and bob (password ann-password, bob-password)."},
7
+ {"key": "sample-charts", "title": "Sample Charts", "alias": "sample-charts", "icon": "chart-column", "file": "samples/sample-charts.sql",
8
+ "description": "All 22 chart types on a page per question (comparing categories, change over time, parts of a whole, relations and spread, plans) and a drill-down from a chart to a report, each page explaining how the query shapes the chart. Demo account demo / demo."},
9
+ {"key": "sample-cards", "title": "Sample Cards", "alias": "sample-cards", "icon": "layout-grid", "file": "samples/sample-cards.sql",
10
+ "description": "Cards with a title, subtitle, body, badge and icon that open a form; KPI tiles; lists shown as cards, badges, links and tabs; and the built-in template components (metric cards, media list, badges, avatars). Demo account demo / demo."},
11
+ {"key": "sample-calendar", "title": "Sample Calendar", "alias": "sample-calendar", "icon": "calendar-days", "file": "samples/sample-calendar.sql",
12
+ "description": "The calendar region: month, week, day and list views, all-day, timed and multi-day events around today, create on click, drag and drop through a PL/pgSQL function with its own rule, and a calendar filtered by an item. Demo account demo / demo."},
13
+ {"key": "sample-grids", "title": "Sample Interactive Grids", "alias": "sample-grids", "icon": "table", "file": "samples/sample-grids.sql",
14
+ "description": "The interactive grid: an editable grid made by the wizard; select lists, required columns, totals, a frozen column, a row menu and a column layout; master-detail grids; and a read-only grid with the single row, group by and chart views. Demo account demo / demo."},
15
+ {"key": "sample-dynamic-actions", "title": "Sample Dynamic Actions", "alias": "sample-dynamic-actions", "icon": "zap", "file": "samples/sample-dynamic-actions.sql",
16
+ "description": "Behaviour without JavaScript of your own: show, hide, enable on a condition; values computed on the server; a report refreshed when an item changes; alerts, confirmations, a wait popup, classes, item errors and an inline dialog. Demo account demo / demo."},
17
+ {"key": "sample-reporting", "title": "Sample Reporting", "alias": "sample-reporting", "icon": "file-text", "file": "samples/sample-reporting.sql",
18
+ "description": "Reports and the ways to search them: an interactive report with its form, a faceted search, smart filters, and a classic report with column display options, a column template and a link. Demo account demo / demo."},
19
+ {"key": "ux-patterns", "title": "UX Pattern Catalog", "alias": "ux-patterns", "icon": "palette", "file": "samples/ux-patterns.sql",
20
+ "description": "The theme's building blocks to copy from: region templates and template options, button styles, item types and form layouts, alerts, badges, colour classes, lists, media lists, timelines, comments, avatars, metric cards, empty states, dialogs, drawers and side columns. Demo account demo / demo."}
7
21
  ],
8
22
  "starters": [
9
23
  {"key": "team-tasks", "title": "Team Tasks", "icon": "layout-grid", "file": "starters/team-tasks.json",
@@ -16,5 +30,13 @@
16
30
  "description": "Assets with their tags, locations, owners and purchase data: a faceted search, an editable grid of locations, a chart by category and a dashboard."},
17
31
  {"key": "event-registrations", "title": "Event Registrations", "icon": "calendar-days", "file": "starters/event-registrations.json",
18
32
  "description": "Events in a calendar and the people who registered for them, with reports, forms and a dashboard."}
33
+ ],
34
+ "utilities": [
35
+ {"key": "lookup-lists", "title": "Lookup Lists", "icon": "list-checks", "file": "utilities/lookup-lists.json",
36
+ "description": "The lists of values your applications share (priorities, statuses, countries) kept as data: lists and their values in a master-detail page and an editable grid, and how other applications of the workspace use them."},
37
+ {"key": "release-notes", "title": "Release Notes", "icon": "git-branch", "file": "utilities/release-notes.json",
38
+ "description": "The releases of your applications and what changed in each (features, fixes, security, breaking changes): reports and forms, releases with their changes, a faceted search of the changes, a release calendar and a dashboard."},
39
+ {"key": "announcements", "title": "Announcements", "icon": "megaphone", "file": "utilities/announcements.json",
40
+ "description": "A notice board for the workspace: planned maintenance, outages and new releases with their audience and dates, as cards, a calendar and a dashboard, and the query other applications use to show the current ones."}
19
41
  ]
20
42
  }