pgkiln 0.87.0 → 0.89.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.
@@ -0,0 +1,13 @@
1
+ -- Sprint 168: the whole application offline from the first visit.
2
+ --
3
+ -- pwa_prefetch_hours with "Keep visited pages on the device" on: after a sign-in (or on the first page
4
+ -- opened on a device) the service worker fetches every page of the navigation menu and
5
+ -- navigation bar the user may open, and keeps them for offline use; again when the kept
6
+ -- copies are older than this many hours. Null: only the pages the user opens.
7
+ -- Prefetched pages run read-only on the server (routes.ts: X-Pgkiln-Prefetch).
8
+
9
+ alter table meta.app add column pwa_prefetch_hours integer
10
+ check (pwa_prefetch_hours between 1 and 168);
11
+
12
+ comment on column meta.app.pwa_prefetch_hours is
13
+ 'PWA: fetch every navigation page for offline use after sign-in, and again after this many hours (null: off)';
@@ -7,7 +7,7 @@ open item and open an issue or pull request; see [CONTRIBUTING.md](../CONTRIBUTI
7
7
  Legend: ✅ available · 🟡 partial (see notes) · ❌ not yet · ➖ not planned (a deliberate choice,
8
8
  or better served by the PostgreSQL ecosystem; see the notes and [extensions](guide/15-extensions.md))
9
9
 
10
- Last reviewed: 2026-11-06 against **APEX 26.2** (sprint 167: hidden items with Value Protected off, default values for an interactive grid's new rows, page items in any region type; sprint 164: every App Builder screen in the eleven builder languages (WS-7 leftover); sprint 165: navigation menu badges from SQL, the notification bell and live badges; sprint 162: the SQL Workshop tools and the region and report settings in the eleven builder languages (CD-8); sprint 161: component events of grids, calendars, maps and trees (CD-3); sprint 160: the Workflow Diagram region, an instance's steps with the reached, current and failed ones marked (CD-7); sprint 159: map layers Lines, Polygons and Extruded Polygons with fill and line styles per layer or per row (CD-6); sprint 158: the item types Image Upload, Text Field with autocomplete, Percent Graph and Display Map (CD-5); sprint 157: the process types Close Dialog, Clear Session State, Reset Pagination, User Preferences, Server Side Geocoding, Print Report, Human Task – Create / – Manage and Form – Initialization (CD-4); sprint 156: dynamic action events double click, key down / press, mouse enter / leave, select, resize, scroll, page unload, before refresh and facets change, greater / less and not-in-list conditions, and the actions Cancel Event, Set Style, Collapse / Expand Tree, Show AI Assistant, Trigger Geocoding and Open report dialog (CD-3); sprint 155: APEX's validation types, with an authorization scheme, Always execute and the error's display location (CD-1); sprint 154: one catalogue of server-side condition types for every component, shown in words in the Page Designer (CD-2); sprint 153: Database users in the eleven builder languages (WS-7 leftover); sprint 152: Workspace utilities, the dashboard, Mail queue, Network access, Instance settings and LDAP directories in the eleven builder languages (WS-7 leftover); sprint 151: vector providers with a model in the database, PostgresML (APEX's ONNX models, AP-1 leftover); sprint 150: remote databases, another PostgreSQL database's tables as foreign tables the applications query (REST Enabled SQL, AP-7 leftover); sprint 149: running workflow instances inside parallel branches migrate to a newer version (WS-6 leftover); sprint 148: the AI assistant streams its answers as the model writes them (WS-4 leftover); sprint 147: `pgkiln advise --offline` checks an export's files without a database (AP-10 leftover); sprint 146: the Advisor checks JSON sources' views against their data profile (AP-8 leftover); sprint 145: the texts that offered AI with "Claude or OpenAI" name all four providers: Claude, OpenAI, Gemini and OpenAI-compatible services (AP-11); sprint 144: `pgkiln advise` for CI with text, JSON and SARIF output (APEXlang static analysis / SQLcl CodeScan), security checks in the Advisor, and working copies and locks from the command line and MCP (the Working Copy APIs) (AP-10); sprint 143: the interactive report's 26.2 dialogs: *is one of* / *is not one of* filters with the column's values and counts, *is between* two values (dates with the date picker in the application's format), relative periods on date columns, and the column order by drag and drop or arrow buttons, followed by downloads and the PDF (AP-9); sprint 142: JSON sources with a data profile and updatable views whose arrays are rows, document views of a table with its children (APEX's duality views), JSON path columns in reports (AP-8); sprint 141: static workspace files (`#WORKSPACE_FILES#`), remote servers for REST data sources, catalogs and AI services (an environment variable per environment), and workspace-level web credentials (AP-7); sprint 140: the items Shuttle (ordered), Select One and Select Many, the regions Classic Report (report templates, pagination types), Breadcrumb and Help Text with a page help text, and a [mapping of every APEX item and region type](#item-and-region-types-one-by-one) (AP-4); sprint 139: OpenTelemetry, traces with a span per SQL statement, web call, AI request and tool call, logs, metrics and `/metrics` for Prometheus, browser spans relayed by pgkiln, per workspace off or its own collector (AP-6); sprint 138: an application's Utilities tab with a dashboard, database object dependencies, change history per component, Upgrade application, UI defaults (APEX's Attribute Dictionary), application and page checksums and an icon browser (AP-5); sprint 137: Create page as a grid of page types with previews, the page types interactive report, smart filters, search page, data loading, tree, timeline, media list, content row, comments, dashboard, wizard, unified task list and workflow console, a Feature tab with nine features that install once, and Create page as copy with shared components mapped or copied (AP-3); sprint 136: vector providers (an AI service with an embedding model or a SQL function), `meta.embed`, *Add vector column* in Table Properties with an HNSW index and a queue that keeps embeddings current, search types vector and hybrid (reciprocal rank fusion with full-text) and a Retrieve (RAG) tool for AI agents, all as the app's role through RLS, on pgvector (AP-1); sprint 135: full-text search configurations, APEX's Oracle Text search type on PostgreSQL full-text search with stemming, ranking, highlights, *Did you mean* and *Add full-text search* in Table Properties (AP-2); sprint 134: the builder also in Italiano, 中文 (简体), 中文 (繁體), 日本語 and 한국어, APEX's ten builder languages plus Nederlands (SEC-5); sprints 121, 123–125: the builder in Deutsch, English, Español, Français, Nederlands and Português, step by step: the frame, sign-in, Workspace home, App Builder, Page Designer menus, the SQL Workshop's home, SQL Worksheet, Object Browser and Table Properties, Shared Components, application settings, users, identity providers, developers and workspaces (WS-7); sprint 122: *Ask in your own words* sets faceted search and smart filter facets, smart filters read a typed phrase (WS-5), and Sample Maps, Sample Approvals, Sample Data Loading and Sample Files in the App Gallery (WS-8); sprint 120: the App Gallery's single-topic sample apps (charts, cards, calendar, interactive grids, dynamic actions, reporting), the UX Pattern Catalog and a Utility Apps tab with Lookup Lists, Release Notes and Announcements (WS-8); sprint 119: data security per table, a roles × columns matrix and masked values per application role in a masking view, the same identity for pages, AI tools, REST, AutoREST and MCP (WS-13); sprint 118: Administration → Mail queue with delivery per hour and per workspace, the slowest provider calls and failures with Retry and Purge, a workspace's own mail service, and workspace network access lists that narrow the installation's allow-list (WS-9); sprint 117: Pillar Light and Pillar Dark navigation appearances with a header strip, clearer navigation tree states, subtle colour utilities, RTL; sprint 116: the comparison moved from 26.1 to 26.2, with a section for the 26.2 workspace items WS-1 … WS-13 and OCI managed identities; the App Builder list with Updated by, working copies and the lock; `pgkiln import --only`, a file-level import; stale rows on facet menus and blueprints fixed; sprint 115: popup LOVs that load on scroll, Wait Popup dynamic actions and dynamic actions on menu entries; sprint 114: Workflow 26.2 with custom task outcomes, task events and notifications, Send E-Mail and draft steps, migrating running instances and the diagram as SVG / PNG; sprint 113: Team Development with issues, labels, milestones, templates, a board and a set-up wizard; sprint 112: AI interactive reports with chips, Undo, computed columns and a phrase parser that needs no AI service; sprint 111: AI Agents with tools as a shared component, a Navigate tool with checksummed URLs, Gemini and OpenAI-compatible services; sprint 110: Data Reporter as a workspace tool with datasets, reporting applications, editors and viewers, published reports with stable URLs and pins; sprint 109: the Workspace home above the App Builder and SQL Workshop, with a summary, top applications, users and developers, and an optional release check; sprint 108: the code editor's syntax colours in the builder's light and dark themes, read-only SQL highlighted, the Data Modeler in dark mode; sprint 107: a transaction mode in the SQL Worksheet with Commit and Rollback; DA-18: JavaScript (MLE) not planned, plv8 where installed; sprint 106: the change log of DDL change sets with Flyway, Liquibase and SQL downloads; sprint 104: RESTful Services modules, templates and handlers with PL/pgSQL and media handlers, privileges, OpenAPI and Try it; sprint 103: Schema Export / Import with pg_dump and pg_restore; sprint 102: the SQL Monitor with live sessions, Top SQL and a statement's plan; sprint 101: Scheduling of pg_cron jobs with a schedule builder, run now and run history; sprint 100: RESTful Services, tables and views published as REST endpoints with paging, filters, keys and OpenAPI; sprint 99: Administration → Database Users for PostgreSQL roles; sprint 98: the Data Modeler with relational diagrams, DDL and a data dictionary; sprint 97: charts of a query in the worksheet, saved charts and their dashboard; sprint 96: the SQL Workshop's Launchpad with pins and tool groups, the builder's search with Ctrl+K; sprint 95: Table Properties: create, edit or copy a table in a form with a DDL preview and Apply in one transaction; sprint 94: an object's menu with Open in a panel, Quick DDL, Add index / trigger / policy and Drop with dependents; sprint 93: worksheets saved as SQL Scripts with tabs and a Files list, Format, Convert case, find and replace, bind and substitution variables; sprint 92: the worksheet's Query Result with sort, value dialog, copy and downloads, plans as a tree with the slowest step and misestimates, SQL History with status, search and run again; sprint 91: the SQL Worksheet like Database Actions' SQL: navigator, Run Statement at the cursor and Run Script with a log per statement, Explain Plan, Autotrace, notices, history, Run as an application role; sprint 90: Comment Out in the page designer, gallery labels in title case; sprint 89: the Object Browser's schema, one row of tabs, blank NULLs, Modify column; sprint 88: home cards with colour bands and round icons, the hero on the page background, a page footer with the version and Back to top; sprint 87: the Create Application wizard reloads the page list for a schema, RLS badges, the alias from the name; sprint 86: the page designer by page number, HTML error pages in the builder; sprint 85: the Form Designer, beyond APEX; sprint 84: the date picker, facet menus; sprint 83: Component Settings and Component Groups; sprint 82: Report Queries; sprint 81: Application Computations and Shortcuts in Shared Components; sprint 80: the master grid's first row selected when the page opens and after a search; grid cells as text with their format mask until edited, with cell navigation by arrow keys, Enter, F2 and Escape; sprint 79: generated dashboards with sums, donuts and lines, leaving out charts that say nothing; applications open in their own light or dark mode until the user chooses, per application; a user menu with Appearance and Sign in when signed out; the developer toolbar's App and Page texts and debug state; the report search's column scope in the magnifier; sprint 78: generated faceted search reports with one column per foreign key, the faceted search's total row count, Show all and Yes/No values, one search field, friendly headings and number formats in generated grids and master-detail pages, friendly names in the Create Application wizard's page list; sprint 77: the Page Designer's layout and tree by display position, button positions, the tree's context menu with Duplicate and Copy to other page, Show Common / Show All, Go to group and the Template Options dialog; display positions, button positions and side-column page templates at runtime; sprint 76: the interactive report's menus and pagination, date items in the date format; sprint 75: the SQL Workshop's Object Browser and tabs; sprint 74: the builder's information architecture: Shared Components grid, Settings tabs, application home search and tasks; sprint 73: the Create Application wizard on one page with page types and features; sprint 72: the runtime chrome compared with APEX 26.2's: the developer toolbar, the Iris look, the navigation bar's Help menu and Install button; sprint 71: generated applications compared with APEX 26.2's: labels, foreign key names in reports, file columns, format masks, the Home page and the dashboard; sprint 70: treemap, sunburst and timeline charts; sprint 69: dragging tasks on a Gantt chart; sprint 68: the Data Reporter's welcome page, Find my workspace; sprint 67: REST source catalogs from OpenAPI documents, a workspace's export and import (two rows added); sprint 66: a blueprint from an existing application; sprint 65: facet charts; sprint 64: approval vacation rules and expiration policies; sprint 63: range, box plot and stock charts; sprint 62: workspace preferences (SQL Workshop and RESTful services per workspace), extension links, Manage backups across applications, SQL Workshop utilities with object reports, About Database and a database monitor; sprint 61: the builder's screens compared with APEX 26.1: the workspace sign-in page, the Administration menu with workspace message, environment banner and utilization, the SQL Workshop home, the App Gallery, application groups and cross application reports (four rows added); sprint 60: isolated workspaces with their own database logins, row-level security on pgkiln's tables and workspace database roles, workspace administrators; sprint 59: the Page Designer's help pane, Quick edit from the developer toolbar; sprint 58: page groups; sprint 57: region templates hero, title bar, buttons container, inline dialog and drawer, wizard with wizard progress, the carousel; sprint 56: Generate DDL and Schema Comparison in the SQL Workshop, `pgkiln ddl` and `pgkiln compare`; sprint 55: an editable login page with a Login region and the Minimal page template, Forgot password through the application's mail service; sprint 54: interactive grid views, the single row view, group by and chart; sprint 53: application settings, user preferences and items kept per user, dynamic translations, `meta.data_export` and data export downloads, display image and geocoded address items; sprint 52: every dynamic action event and action, Ajax callbacks, an error handling function, the session timeout warning; sprint 51: checked imports, application backups, availability and Run only, feedback, type-specific attribute help; sprint 50: lost update detection, warn on unsaved changes, the URL and search regions with search configurations, the developer toolbar with a session state viewer, and e-mail through SMTP with OAuth2, Microsoft Graph, Amazon SES and HTTP APIs; import checks, application availability, property help and e-mail compared again; 2026-10-06: pgkiln 0.31.0, in progress: static application files and the Execute JavaScript dynamic action; region, item, dynamic action and process plug-ins; conditional and dynamic theme styles; XML and Excel in `meta.parse_data`, zips in SQL, `meta.v_boolean`; file items in S3-compatible object storage; map layers by the visible area and as vector tiles; a graphical query builder; tenants for workflows and tasks; application files as YAML and region static IDs; Lucide icons and icon modifiers; PWA push notifications; 0.30.0: 136 icons and an icon picker; an axe-core accessibility audit; cropping pictures; session sharing between applications; twenty-two built-in languages; 0.29.0: workspaces; the Iris base style; drawers; built-in template components including the metric card; Theme Roller dark-mode colours, live preview and template options on items and report columns; report selection across pages; instance settings; fourteen built-in languages; 0.28.0: AI with Claude and OpenAI; 0.27.0: Data Reporter; sample data generator; create application from several sheets, pasted data or existing tables; 0.26.0: working copies with a three-way merge; theme, library and boilerplate applications with subscriptions; 0.25.0: Gantt, pyramid and polar charts; map layers, clustering and server-side spatial filters; REST data sources that write back from forms and grids and synchronise into tables, OAuth2 password and refresh-token grants; debug messages per request and an install/upgrade log; `meta.web_request()` and `meta.parse_data()`; Theme Roller style variants and template options; 0.24.0: automations with several actions, error handling per row and runs from SQL; invoke-API workflow steps; Unload Data in the Data Workshop; create page wizards for cards, calendar, chart, map, faceted search, form and master-detail; create an application from a file; 0.23.0: interactive grid aggregates, frozen/moved/resized columns, layouts and saved grid reports, master-detail, row actions and copy/paste; download processes, execution chains in the background, workflow processes, branches to a function's URL or another app, the Dialog Closed event; number format masks, automatic time zone, runtime messages in German, French and Spanish; SQL scripts, Quick SQL, a query builder, XML loading and data load definitions; custom authentication, lists, page and application locks with developer comments, supporting objects; 0.22.0: keyset paging, report PDFs and REST collections streamed from a cursor, database-account authentication; 0.21.0: HTTP-header authentication behind a reverse proxy; 0.20.0: a popup list of values with server-side search; 0.19.0: row-range pagination, maximum row counts and row limits, lazy loading and caching of regions, streamed CSV/Excel downloads; 0.18.0: calendar week/day/list views with drag and drop, bubble/gauge/funnel/radar charts and drill-down, smart filters, a region display selector and more facet types, computations, conditional branches, build options, menu buttons, REST data sources and web credentials, rich text/Markdown, rating, combobox, date range and QR code items; 0.17.0: an App Builder home, workspace dashboard and utilities like APEX's; 0.16.0: a Page Designer with drag-and-drop layout, a code editor with autocomplete, template components and plug-ins, parallel branches and versions of workflows, the `pgkiln` command line with one file per component; 0.15.0: drop and paste files, heat maps, filtering a report by the map area; 0.14.0: stacked, combo, scatter and pie charts, several files per upload item; 0.13.0: map and tree regions; 0.12.0: REST modules in the builder; 0.11.0: workflows and Progressive Web Apps; 0.10.0: LDAP, SAML, "Keep me signed in", document templates, JSON loading, approvals and the task list; pgkiln installs no application, HR is an example).
10
+ Last reviewed: 2026-11-06 against **APEX 26.2** (sprint 168: every page of the navigation offline from the first visit; sprint 167: hidden items with Value Protected off, default values for an interactive grid's new rows, page items in any region type; sprint 164: every App Builder screen in the eleven builder languages (WS-7 leftover); sprint 165: navigation menu badges from SQL, the notification bell and live badges; sprint 162: the SQL Workshop tools and the region and report settings in the eleven builder languages (CD-8); sprint 161: component events of grids, calendars, maps and trees (CD-3); sprint 160: the Workflow Diagram region, an instance's steps with the reached, current and failed ones marked (CD-7); sprint 159: map layers Lines, Polygons and Extruded Polygons with fill and line styles per layer or per row (CD-6); sprint 158: the item types Image Upload, Text Field with autocomplete, Percent Graph and Display Map (CD-5); sprint 157: the process types Close Dialog, Clear Session State, Reset Pagination, User Preferences, Server Side Geocoding, Print Report, Human Task – Create / – Manage and Form – Initialization (CD-4); sprint 156: dynamic action events double click, key down / press, mouse enter / leave, select, resize, scroll, page unload, before refresh and facets change, greater / less and not-in-list conditions, and the actions Cancel Event, Set Style, Collapse / Expand Tree, Show AI Assistant, Trigger Geocoding and Open report dialog (CD-3); sprint 155: APEX's validation types, with an authorization scheme, Always execute and the error's display location (CD-1); sprint 154: one catalogue of server-side condition types for every component, shown in words in the Page Designer (CD-2); sprint 153: Database users in the eleven builder languages (WS-7 leftover); sprint 152: Workspace utilities, the dashboard, Mail queue, Network access, Instance settings and LDAP directories in the eleven builder languages (WS-7 leftover); sprint 151: vector providers with a model in the database, PostgresML (APEX's ONNX models, AP-1 leftover); sprint 150: remote databases, another PostgreSQL database's tables as foreign tables the applications query (REST Enabled SQL, AP-7 leftover); sprint 149: running workflow instances inside parallel branches migrate to a newer version (WS-6 leftover); sprint 148: the AI assistant streams its answers as the model writes them (WS-4 leftover); sprint 147: `pgkiln advise --offline` checks an export's files without a database (AP-10 leftover); sprint 146: the Advisor checks JSON sources' views against their data profile (AP-8 leftover); sprint 145: the texts that offered AI with "Claude or OpenAI" name all four providers: Claude, OpenAI, Gemini and OpenAI-compatible services (AP-11); sprint 144: `pgkiln advise` for CI with text, JSON and SARIF output (APEXlang static analysis / SQLcl CodeScan), security checks in the Advisor, and working copies and locks from the command line and MCP (the Working Copy APIs) (AP-10); sprint 143: the interactive report's 26.2 dialogs: *is one of* / *is not one of* filters with the column's values and counts, *is between* two values (dates with the date picker in the application's format), relative periods on date columns, and the column order by drag and drop or arrow buttons, followed by downloads and the PDF (AP-9); sprint 142: JSON sources with a data profile and updatable views whose arrays are rows, document views of a table with its children (APEX's duality views), JSON path columns in reports (AP-8); sprint 141: static workspace files (`#WORKSPACE_FILES#`), remote servers for REST data sources, catalogs and AI services (an environment variable per environment), and workspace-level web credentials (AP-7); sprint 140: the items Shuttle (ordered), Select One and Select Many, the regions Classic Report (report templates, pagination types), Breadcrumb and Help Text with a page help text, and a [mapping of every APEX item and region type](#item-and-region-types-one-by-one) (AP-4); sprint 139: OpenTelemetry, traces with a span per SQL statement, web call, AI request and tool call, logs, metrics and `/metrics` for Prometheus, browser spans relayed by pgkiln, per workspace off or its own collector (AP-6); sprint 138: an application's Utilities tab with a dashboard, database object dependencies, change history per component, Upgrade application, UI defaults (APEX's Attribute Dictionary), application and page checksums and an icon browser (AP-5); sprint 137: Create page as a grid of page types with previews, the page types interactive report, smart filters, search page, data loading, tree, timeline, media list, content row, comments, dashboard, wizard, unified task list and workflow console, a Feature tab with nine features that install once, and Create page as copy with shared components mapped or copied (AP-3); sprint 136: vector providers (an AI service with an embedding model or a SQL function), `meta.embed`, *Add vector column* in Table Properties with an HNSW index and a queue that keeps embeddings current, search types vector and hybrid (reciprocal rank fusion with full-text) and a Retrieve (RAG) tool for AI agents, all as the app's role through RLS, on pgvector (AP-1); sprint 135: full-text search configurations, APEX's Oracle Text search type on PostgreSQL full-text search with stemming, ranking, highlights, *Did you mean* and *Add full-text search* in Table Properties (AP-2); sprint 134: the builder also in Italiano, 中文 (简体), 中文 (繁體), 日本語 and 한국어, APEX's ten builder languages plus Nederlands (SEC-5); sprints 121, 123–125: the builder in Deutsch, English, Español, Français, Nederlands and Português, step by step: the frame, sign-in, Workspace home, App Builder, Page Designer menus, the SQL Workshop's home, SQL Worksheet, Object Browser and Table Properties, Shared Components, application settings, users, identity providers, developers and workspaces (WS-7); sprint 122: *Ask in your own words* sets faceted search and smart filter facets, smart filters read a typed phrase (WS-5), and Sample Maps, Sample Approvals, Sample Data Loading and Sample Files in the App Gallery (WS-8); sprint 120: the App Gallery's single-topic sample apps (charts, cards, calendar, interactive grids, dynamic actions, reporting), the UX Pattern Catalog and a Utility Apps tab with Lookup Lists, Release Notes and Announcements (WS-8); sprint 119: data security per table, a roles × columns matrix and masked values per application role in a masking view, the same identity for pages, AI tools, REST, AutoREST and MCP (WS-13); sprint 118: Administration → Mail queue with delivery per hour and per workspace, the slowest provider calls and failures with Retry and Purge, a workspace's own mail service, and workspace network access lists that narrow the installation's allow-list (WS-9); sprint 117: Pillar Light and Pillar Dark navigation appearances with a header strip, clearer navigation tree states, subtle colour utilities, RTL; sprint 116: the comparison moved from 26.1 to 26.2, with a section for the 26.2 workspace items WS-1 … WS-13 and OCI managed identities; the App Builder list with Updated by, working copies and the lock; `pgkiln import --only`, a file-level import; stale rows on facet menus and blueprints fixed; sprint 115: popup LOVs that load on scroll, Wait Popup dynamic actions and dynamic actions on menu entries; sprint 114: Workflow 26.2 with custom task outcomes, task events and notifications, Send E-Mail and draft steps, migrating running instances and the diagram as SVG / PNG; sprint 113: Team Development with issues, labels, milestones, templates, a board and a set-up wizard; sprint 112: AI interactive reports with chips, Undo, computed columns and a phrase parser that needs no AI service; sprint 111: AI Agents with tools as a shared component, a Navigate tool with checksummed URLs, Gemini and OpenAI-compatible services; sprint 110: Data Reporter as a workspace tool with datasets, reporting applications, editors and viewers, published reports with stable URLs and pins; sprint 109: the Workspace home above the App Builder and SQL Workshop, with a summary, top applications, users and developers, and an optional release check; sprint 108: the code editor's syntax colours in the builder's light and dark themes, read-only SQL highlighted, the Data Modeler in dark mode; sprint 107: a transaction mode in the SQL Worksheet with Commit and Rollback; DA-18: JavaScript (MLE) not planned, plv8 where installed; sprint 106: the change log of DDL change sets with Flyway, Liquibase and SQL downloads; sprint 104: RESTful Services modules, templates and handlers with PL/pgSQL and media handlers, privileges, OpenAPI and Try it; sprint 103: Schema Export / Import with pg_dump and pg_restore; sprint 102: the SQL Monitor with live sessions, Top SQL and a statement's plan; sprint 101: Scheduling of pg_cron jobs with a schedule builder, run now and run history; sprint 100: RESTful Services, tables and views published as REST endpoints with paging, filters, keys and OpenAPI; sprint 99: Administration → Database Users for PostgreSQL roles; sprint 98: the Data Modeler with relational diagrams, DDL and a data dictionary; sprint 97: charts of a query in the worksheet, saved charts and their dashboard; sprint 96: the SQL Workshop's Launchpad with pins and tool groups, the builder's search with Ctrl+K; sprint 95: Table Properties: create, edit or copy a table in a form with a DDL preview and Apply in one transaction; sprint 94: an object's menu with Open in a panel, Quick DDL, Add index / trigger / policy and Drop with dependents; sprint 93: worksheets saved as SQL Scripts with tabs and a Files list, Format, Convert case, find and replace, bind and substitution variables; sprint 92: the worksheet's Query Result with sort, value dialog, copy and downloads, plans as a tree with the slowest step and misestimates, SQL History with status, search and run again; sprint 91: the SQL Worksheet like Database Actions' SQL: navigator, Run Statement at the cursor and Run Script with a log per statement, Explain Plan, Autotrace, notices, history, Run as an application role; sprint 90: Comment Out in the page designer, gallery labels in title case; sprint 89: the Object Browser's schema, one row of tabs, blank NULLs, Modify column; sprint 88: home cards with colour bands and round icons, the hero on the page background, a page footer with the version and Back to top; sprint 87: the Create Application wizard reloads the page list for a schema, RLS badges, the alias from the name; sprint 86: the page designer by page number, HTML error pages in the builder; sprint 85: the Form Designer, beyond APEX; sprint 84: the date picker, facet menus; sprint 83: Component Settings and Component Groups; sprint 82: Report Queries; sprint 81: Application Computations and Shortcuts in Shared Components; sprint 80: the master grid's first row selected when the page opens and after a search; grid cells as text with their format mask until edited, with cell navigation by arrow keys, Enter, F2 and Escape; sprint 79: generated dashboards with sums, donuts and lines, leaving out charts that say nothing; applications open in their own light or dark mode until the user chooses, per application; a user menu with Appearance and Sign in when signed out; the developer toolbar's App and Page texts and debug state; the report search's column scope in the magnifier; sprint 78: generated faceted search reports with one column per foreign key, the faceted search's total row count, Show all and Yes/No values, one search field, friendly headings and number formats in generated grids and master-detail pages, friendly names in the Create Application wizard's page list; sprint 77: the Page Designer's layout and tree by display position, button positions, the tree's context menu with Duplicate and Copy to other page, Show Common / Show All, Go to group and the Template Options dialog; display positions, button positions and side-column page templates at runtime; sprint 76: the interactive report's menus and pagination, date items in the date format; sprint 75: the SQL Workshop's Object Browser and tabs; sprint 74: the builder's information architecture: Shared Components grid, Settings tabs, application home search and tasks; sprint 73: the Create Application wizard on one page with page types and features; sprint 72: the runtime chrome compared with APEX 26.2's: the developer toolbar, the Iris look, the navigation bar's Help menu and Install button; sprint 71: generated applications compared with APEX 26.2's: labels, foreign key names in reports, file columns, format masks, the Home page and the dashboard; sprint 70: treemap, sunburst and timeline charts; sprint 69: dragging tasks on a Gantt chart; sprint 68: the Data Reporter's welcome page, Find my workspace; sprint 67: REST source catalogs from OpenAPI documents, a workspace's export and import (two rows added); sprint 66: a blueprint from an existing application; sprint 65: facet charts; sprint 64: approval vacation rules and expiration policies; sprint 63: range, box plot and stock charts; sprint 62: workspace preferences (SQL Workshop and RESTful services per workspace), extension links, Manage backups across applications, SQL Workshop utilities with object reports, About Database and a database monitor; sprint 61: the builder's screens compared with APEX 26.1: the workspace sign-in page, the Administration menu with workspace message, environment banner and utilization, the SQL Workshop home, the App Gallery, application groups and cross application reports (four rows added); sprint 60: isolated workspaces with their own database logins, row-level security on pgkiln's tables and workspace database roles, workspace administrators; sprint 59: the Page Designer's help pane, Quick edit from the developer toolbar; sprint 58: page groups; sprint 57: region templates hero, title bar, buttons container, inline dialog and drawer, wizard with wizard progress, the carousel; sprint 56: Generate DDL and Schema Comparison in the SQL Workshop, `pgkiln ddl` and `pgkiln compare`; sprint 55: an editable login page with a Login region and the Minimal page template, Forgot password through the application's mail service; sprint 54: interactive grid views, the single row view, group by and chart; sprint 53: application settings, user preferences and items kept per user, dynamic translations, `meta.data_export` and data export downloads, display image and geocoded address items; sprint 52: every dynamic action event and action, Ajax callbacks, an error handling function, the session timeout warning; sprint 51: checked imports, application backups, availability and Run only, feedback, type-specific attribute help; sprint 50: lost update detection, warn on unsaved changes, the URL and search regions with search configurations, the developer toolbar with a session state viewer, and e-mail through SMTP with OAuth2, Microsoft Graph, Amazon SES and HTTP APIs; import checks, application availability, property help and e-mail compared again; 2026-10-06: pgkiln 0.31.0, in progress: static application files and the Execute JavaScript dynamic action; region, item, dynamic action and process plug-ins; conditional and dynamic theme styles; XML and Excel in `meta.parse_data`, zips in SQL, `meta.v_boolean`; file items in S3-compatible object storage; map layers by the visible area and as vector tiles; a graphical query builder; tenants for workflows and tasks; application files as YAML and region static IDs; Lucide icons and icon modifiers; PWA push notifications; 0.30.0: 136 icons and an icon picker; an axe-core accessibility audit; cropping pictures; session sharing between applications; twenty-two built-in languages; 0.29.0: workspaces; the Iris base style; drawers; built-in template components including the metric card; Theme Roller dark-mode colours, live preview and template options on items and report columns; report selection across pages; instance settings; fourteen built-in languages; 0.28.0: AI with Claude and OpenAI; 0.27.0: Data Reporter; sample data generator; create application from several sheets, pasted data or existing tables; 0.26.0: working copies with a three-way merge; theme, library and boilerplate applications with subscriptions; 0.25.0: Gantt, pyramid and polar charts; map layers, clustering and server-side spatial filters; REST data sources that write back from forms and grids and synchronise into tables, OAuth2 password and refresh-token grants; debug messages per request and an install/upgrade log; `meta.web_request()` and `meta.parse_data()`; Theme Roller style variants and template options; 0.24.0: automations with several actions, error handling per row and runs from SQL; invoke-API workflow steps; Unload Data in the Data Workshop; create page wizards for cards, calendar, chart, map, faceted search, form and master-detail; create an application from a file; 0.23.0: interactive grid aggregates, frozen/moved/resized columns, layouts and saved grid reports, master-detail, row actions and copy/paste; download processes, execution chains in the background, workflow processes, branches to a function's URL or another app, the Dialog Closed event; number format masks, automatic time zone, runtime messages in German, French and Spanish; SQL scripts, Quick SQL, a query builder, XML loading and data load definitions; custom authentication, lists, page and application locks with developer comments, supporting objects; 0.22.0: keyset paging, report PDFs and REST collections streamed from a cursor, database-account authentication; 0.21.0: HTTP-header authentication behind a reverse proxy; 0.20.0: a popup list of values with server-side search; 0.19.0: row-range pagination, maximum row counts and row limits, lazy loading and caching of regions, streamed CSV/Excel downloads; 0.18.0: calendar week/day/list views with drag and drop, bubble/gauge/funnel/radar charts and drill-down, smart filters, a region display selector and more facet types, computations, conditional branches, build options, menu buttons, REST data sources and web credentials, rich text/Markdown, rating, combobox, date range and QR code items; 0.17.0: an App Builder home, workspace dashboard and utilities like APEX's; 0.16.0: a Page Designer with drag-and-drop layout, a code editor with autocomplete, template components and plug-ins, parallel branches and versions of workflows, the `pgkiln` command line with one file per component; 0.15.0: drop and paste files, heat maps, filtering a report by the map area; 0.14.0: stacked, combo, scatter and pie charts, several files per upload item; 0.13.0: map and tree regions; 0.12.0: REST modules in the builder; 0.11.0: workflows and Progressive Web Apps; 0.10.0: LDAP, SAML, "Keep me signed in", document templates, JSON loading, approvals and the task list; pgkiln installs no application, HR is an example).
11
11
 
12
12
  ## At a glance
13
13
 
@@ -256,7 +256,7 @@ review is a diff. Not counted in [At a glance](#at-a-glance): the rows above com
256
256
  | Accessibility | ✅ | Labels, keyboard, focus rings, reduced motion, table alternatives for charts, scrolling tables reachable from the keyboard; an automated WCAG 2.1 A/AA audit (axe-core) of every example page in light, dark and Iris and of the builder's main pages runs in CI with no violations allowed. Not a manual expert audit |
257
257
  | Drawers, top/bottom dialogs (26.1) | ✅ | Modal pages open as a centred dialog or as a drawer from the left, right, top or bottom edge, in three sizes, sliding in (without animation for reduced motion); on phones full screen ([chapter 4](guide/04-pages-and-regions.md#dialogs-and-drawers)). Not yet: a footer slot for inline drawers |
258
258
  | New "Iris" default style (26.1) | ✅ | Base style **Iris**, the default for new applications, light by default with the user's choice of dark: (sprint 72) like APEX 26's: a white body, a dark warm-grey header with an accent strip and a dark menu, 4 px corners, region headers with a rule, a dark primary button, a large page title; light and dark palettes checked for WCAG AA contrast. The earlier look stays as **Lavender** ([chapter 14](guide/14-globalization.md#base-styles-iris-lavender-and-standard)) |
259
- | Progressive Web App (push notifications: 23.1) | ✅ | Per app: installable (manifest, icon, standalone), service worker, offline pages (opt-in, wiped at sign-in/out), **forms sent offline queued on the device and sent later** (files included, once only, under the same user), location, camera and barcode items. (0.31) **Push notifications**: users turn them on per device (My account, or the dynamic action `push_subscribe`); `meta.send_push(user, title, body, page, items)` and a `send_push` process queue them, sent after the commit, encrypted for the device (RFC 8291) and signed with the application's own VAPID key (RFC 8292, stored encrypted, never exported); links signed for the recipient; devices end at sign-out, a new password, deactivation or removed access ([chapter 17](guide/17-mobile.md#push-notifications)). No images or action buttons in notifications |
259
+ | Progressive Web App (push notifications: 23.1) | ✅ | Per app: installable (manifest, icon, standalone), service worker, offline pages (opt-in, wiped at sign-in/out), **forms sent offline queued on the device and sent later** (files included, once only, under the same user), location, camera and barcode items. (0.31) **Push notifications**: users turn them on per device (My account, or the dynamic action `push_subscribe`); `meta.send_push(user, title, body, page, items)` and a `send_push` process queue them, sent after the commit, encrypted for the device (RFC 8291) and signed with the application's own VAPID key (RFC 8292, stored encrypted, never exported); links signed for the recipient; devices end at sign-out, a new password, deactivation or removed access ([chapter 17](guide/17-mobile.md#push-notifications)). No images or action buttons in notifications; (168) **every page of the navigation offline** from the first visit (fetched read-only in the background after sign-in, again every N hours), beyond APEX |
260
260
 
261
261
  ## Globalization
262
262
 
@@ -225,9 +225,16 @@ department. Every installation has the **Default** workspace, which holds the ap
225
225
  - **New applications** (Create, from a file, pasted data, existing tables, a blueprint) and **imports** go into the
226
226
  current workspace; a working copy stays in its main application's workspace. Imports made with the command line
227
227
  (`pgkiln import`) go into Default. Exports don't carry the workspace.
228
- - **Workspace utilities**: tiles for users and sign-in, application groups, the App Gallery, developers, the SQL
229
- Workshop, the dashboard, [static workspace files, remote servers and web credentials](#static-workspace-files-remote-servers-and-web-credentials) (and, for administrators, instance settings, workspaces, AI and mail services and the
230
- installation), with **Cross Application Reports** in the side column (APEX: Cross Application Reports), each over
228
+ - **Workspace utilities** (the rail's **Administration**, `/builder/utilities`; UX-4): the utilities in sections with a
229
+ **filter** field at the top: *People and sign-in* (users, developers, identity providers, LDAP directories, password
230
+ policy), *Connections* ([remote servers, web credentials](#static-workspace-files-remote-servers-and-web-credentials),
231
+ remote databases, REST source catalogs, mail services; for workspace administrators the mail queue and network
232
+ access), *Data and AI* (AI services, vector providers, UI defaults, static workspace files, icons), *Operations*
233
+ (application groups, backups, export and import, the dashboard, OpenTelemetry), the current **workspace** (for its
234
+ administrators: its page, [workspace message and environment banner](#workspace-message-and-environment-banner),
235
+ preferences, extension links, [workspace utilization](#workspace-utilization)) and the **instance** (for
236
+ administrators: instance settings, workspaces, [Database users](#database-users), the installation), then
237
+ **Cross Application Reports** (APEX: Cross Application Reports), each over
231
238
  every application of the current workspace: *Application attributes* (type, sign-in, availability, build status,
232
239
  debug level, language, database role), *Build options*, *Developer comments*, *Authentication* (sign-in methods,
233
240
  identity providers, LDAP, access, the login page, Forgot password), *Authorization schemes*, *Password items* and
@@ -247,7 +254,7 @@ its developers must not reach other teams' applications or data.
247
254
  ### Workspace message and environment banner
248
255
 
249
256
  Administrators and the workspace's administrators set them on the workspace's page (*Manage service*, or the
250
- Administration menu → *Workspace message and environment banner*; APEX: Define Workspace Message, Define Environment
257
+ Administration → *Workspace message and environment banner*; APEX: Define Workspace Message, Define Environment
251
258
  Banner): a **message** (plain text, up to 2000 characters) shown to the workspace's developers on the
252
259
  [Workspace home](#workspace-home) (where they can also edit it in place) and the App Builder home,
253
260
  and an **environment banner**: a label of up to 40 characters in one of eight colours, shown at the top of every
@@ -255,7 +262,7 @@ builder page while the workspace is current.
255
262
 
256
263
  ### Workspace preferences
257
264
 
258
- Administrators and the workspace's administrators set them on the workspace's page (Administration menu →
265
+ Administrators and the workspace's administrators set them on the workspace's page (Administration →
259
266
  *Workspace preferences*; APEX: Set Workspace Preferences):
260
267
 
261
268
  - **Enable the SQL Workshop**: off, every SQL Workshop page (the SQL Worksheet, the Object Browser, SQL scripts, Load and
@@ -349,7 +356,7 @@ says what was added, kept and replaced.
349
356
 
350
357
  ### Workspace utilization
351
358
 
352
- Administration menu → **Workspace utilization** (`/builder/workspaces/<id>/utilization`; APEX: Workspace
359
+ Administration → **Workspace utilization** (`/builder/workspaces/<id>/utilization`; APEX: Workspace
353
360
  Utilization), for administrators and the workspace's administrators: the number of applications, pages, developers,
354
361
  page views of the last 30 days and SQL scripts; per application its pages, regions, items, page views, users and the
355
362
  size of its static files; and the schemas the workspace's database roles use with their tables and storage.
@@ -462,11 +469,14 @@ longer sign in. Delete a workspace only when it is shared and empty; its logins
462
469
  Every builder page has the same frame, modelled on an IDE:
463
470
 
464
471
  - **The icon rail** on the left: Home (the [Workspace home](#workspace-home); the logo leads there too), App Builder,
465
- SQL Workshop, [App Gallery](#app-gallery), [Data Reporter](#data-reporter-datasets-and-reporting-applications), **Search** (on the
466
- pages of an application); at the bottom the **Administration** menu (Workspace utilities; for administrators of the
467
- current workspace *Manage service*: [workspace message and environment banner](#workspace-message-and-environment-banner),
468
- [workspace utilization](#workspace-utilization) and the workspace's page; Manage users and groups, Developers,
469
- Monitor activity, [Database users](#database-users) for administrators, Change my password), Help, and your account menu (the circle with your initials) with the
472
+ SQL Workshop, [App Gallery](#app-gallery), [Data Reporter](#data-reporter-datasets-and-reporting-applications),
473
+ Team Development and **Administration** (the workspace utilities, below); at the bottom Help, **Show labels** and your
474
+ account menu (the circle with your initials). (UX-3) The entry of the page you are on is marked, by the page's
475
+ address (an application's pages under App Builder; users, developers, workspaces, instance settings and the other
476
+ utilities under Administration). *Show labels* puts each entry's name next to its icon (*Hide labels* takes them
477
+ away; kept for your session); without them the name shows when you point at an icon or move to it with the
478
+ keyboard. The search is the field at the top of every page (**Ctrl+K**); an application's own search is its
479
+ **Search** tab. Your account menu has the
470
480
  **builder theme** (*Dark*, the default; *Light*; or *System*, which follows the operating system),
471
481
  Change password and Sign out. The theme is remembered on this device, also after signing out;
472
482
  it only changes the builder, not your applications.
@@ -548,6 +558,8 @@ names and help of a shared component's form), the links to this guide, and any S
548
558
  (sprint 109; APEX: the workspace home page) The page the builder opens after signing in, `/builder/home`, above the
549
559
  App Builder and the SQL Workshop. Everything on it is of the current [workspace](#workspaces), and it works without
550
560
  JavaScript. The [default-password banner](#signing-in-and-developer-accounts) stays on top until the password is changed.
561
+ (UX-13) For administrators, **Needs attention** under the heading lists what must not wait: failing
562
+ [lockout checks](#lockout-checks) and, with the release check on, a newer pgkiln (details in the System message).
551
563
 
552
564
  - **Tool cards**: App Builder (`/builder/apps`), SQL Workshop (not when the workspace's preferences turn it off),
553
565
  **Data Reporter** (when the server has the Data Reporter tool at `/builder/reporter`) and the [App Gallery](#app-gallery).
@@ -582,12 +594,13 @@ that may not reach the internet. Under the test runner the server never asks Git
582
594
  The App Builder's home page, `/builder/apps` (`/builder` shows it too, unless the workspace's
583
595
  [preferences](#workspace-preferences) send `/builder` to the Workspace home), is laid out like APEX's App Builder:
584
596
 
585
- - **Four tiles**: **Create** and **Import** (each on its own page), the workspace **Dashboard**
586
- (applications, pages, active accounts; page views, users, failed sign-ins and errors of the last
587
- 24 hours, and per application the last 7 days with a link to its Activity page) and
588
- **Workspace Utilities** (users, identity providers, LDAP directories, password policy,
589
- developers, the SQL Workshop, and for administrators [workspaces](#workspaces), AI services and the
590
- [installation log](#installation)). The list, the dashboard and Recent show the current workspace.
597
+ - **The header** (UX-5): the heading *App Builder*, **Create** (the page's one primary action), **Import** and a
598
+ **⋯** menu with the workspace **Dashboard** (applications, pages, active accounts; page views, users, failed
599
+ sign-ins and errors of the last 24 hours, and per application the last 7 days with a link to its Activity page)
600
+ and **Administration** (the [workspace utilities](#workspaces)). The list, the dashboard and Recent show the
601
+ current workspace. Every builder page follows the same pattern: one heading, one primary (accent) button,
602
+ the other actions beside it or in a ⋯ menu; an application's Pages tab has **Create page** as its primary action
603
+ in the application's header (other tabs: **Run**), and its Tasks list only what the page doesn't offer already.
591
604
  - **The applications**, as a report (application id, name, alias, pages, page views of the last 24
592
605
  hours, sign-in, last updated, **updated by**, **working copies**, and Edit / Run) or as cards. **Search** filters the list as you type
593
606
  (by name, alias or id; without JavaScript, press Enter). Click a column heading, or use
@@ -1072,6 +1085,13 @@ The page designer has three panes, as in APEX's Page Designer. From 1024 pixels
1072
1085
  side by side; on phones and portrait tablets they are tabs (**Tree**, **Layout**, **Properties**):
1073
1086
  choosing a component opens the Properties tab.
1074
1087
 
1088
+ (UX-2) From 1024 pixels wide the designer is fixed to the window: only the panes scroll, so the toolbar
1089
+ with **Save**, Undo / Redo and the page navigator stays in view, and the property editor's own save button
1090
+ stays at the bottom of its pane. **Ctrl+S** (Cmd+S) saves. A change not saved yet puts a dot on Save, and
1091
+ leaving the page with one asks first. On a window under 800 pixels high the gallery under the layout is only
1092
+ its tabs (*Regions*, *Items*, *Buttons*): choose one to open it, choose the open one to fold it again; the
1093
+ designer remembers which you prefer.
1094
+
1075
1095
  - **Left: the component tree**, with four tabs:
1076
1096
  - **Rendering**: the page, *Pre-Rendering* (branches, computations and processes that run
1077
1097
  before the page is shown), the regions by
@@ -1087,7 +1107,8 @@ choosing a component opens the Properties tab.
1087
1107
  application items), linking to Shared Components.
1088
1108
 
1089
1109
  Icons show which components have an authorization scheme, a condition or a build option; type icons are coloured
1090
- by kind (regions, items, buttons, dynamic actions, processes). Folders open and close
1110
+ by kind (regions, items, buttons, dynamic actions, processes). (UX-12) A component's name comes first: when the
1111
+ pane is narrow its type text is left out (the icon shows the type; hover a component for its name and type). Folders open and close
1091
1112
  with a click; with the keyboard, the arrow keys walk the tree (Left / Right close and open a
1092
1113
  folder), Enter opens a component.
1093
1114
 
@@ -1790,7 +1811,7 @@ sending mailboxes (an application access policy or RBAC for Applications).
1790
1811
 
1791
1812
  ### Mail queue
1792
1813
 
1793
- Administration menu → **Mail queue** (`/builder/mail-queue`; APEX 26.2: Administration → Mail queue): the delivery
1814
+ Administration → **Mail queue** (`/builder/mail-queue`; APEX 26.2: Administration → Mail queue): the delivery
1794
1815
  health of the mail queue, for administrators (every workspace, or one) and a workspace's administrators (the current
1795
1816
  workspace's applications only; other developers get 403). Filter by period (24 hours, 48 hours, 7 days), workspace and
1796
1817
  application:
@@ -1835,7 +1856,7 @@ applications off or send them to a collector of their own as well (through the a
1835
1856
 
1836
1857
  ### Network access
1837
1858
 
1838
- Administration menu → **Network access** (`/builder/network`; APEX 26.2: workspace network ACLs), for administrators
1859
+ Administration → **Network access** (`/builder/network`; APEX 26.2: workspace network ACLs), for administrators
1839
1860
  (any workspace, `?ws=`) and a workspace's administrators (their workspaces): which web services a workspace's
1840
1861
  applications may call. The installation's list (`PGKILN_REST_ALLOWED_HOSTS`, set in the server's environment, shown
1841
1862
  read-only; the private hosts to administrators only) applies to every workspace. A workspace can choose **Only the
@@ -2121,7 +2142,8 @@ others (Quick SQL, Query Builder, Load and Unload Data, Sample Data, DDL, Compar
2121
2142
 
2122
2143
  (sprint 96; Database Actions: the Launchpad) The home page starts with **Pinned & recently visited**: the pages you
2123
2144
  pinned (a tool, a database object from the Object Browser, a saved worksheet from its file menu; at most 24, per
2124
- workspace) and the SQL Workshop pages you opened last in this browser. Below, the tools are in three tabs,
2145
+ workspace) and the SQL Workshop pages you opened last in this browser (UX-14: each page once, the latest visit
2146
+ first, without the Launchpad itself or a page that is pinned already). Below, the tools are in three tabs,
2125
2147
  **Development**, **Administration** and **Monitoring**, each with a line about what it does and a pin; the **More**
2126
2148
  menu uses the same headings.
2127
2149
 
@@ -2133,6 +2155,12 @@ the SQL Workshop's connection may use. Without JavaScript the header's field ope
2133
2155
  - **SQL Worksheet** (`/builder/sql/worksheet`, sprint 91; Database Actions: SQL; was SQL Commands, whose address
2134
2156
  `/builder/sql/commands` opens the same page): one screen with a **navigator** on the left, the **editor** with a
2135
2157
  toolbar, **output tabs** below and a **status bar**.
2158
+ - (UX-1) The **toolbar is one row**: the worksheet's file menu, **Run Statement**, **Run Script**, **Explain Plan**
2159
+ and **Autotrace** (icons with their name as a tooltip when the window is narrow), **Suggest** and **Check**, the
2160
+ commit mode, fullscreen and a **⋯ menu** with *Continue on error*, Format, Convert case, Find, Load data, Clear
2161
+ and the keyboard shortcuts. **Run as** is in the status bar (its tooltip says who the statements run as).
2162
+ The editor takes about half of the window and the output the other half (drag the splitter; the size is
2163
+ remembered); before the first run the output is only its row of tabs, and choosing a tab opens it.
2136
2164
  - The navigator has a schema (the same one as the Object Browser), an object type (*All Objects*, tables, views,
2137
2165
  materialized views, indexes, sequences, types, functions, procedures, triggers, policies) and a search field.
2138
2166
  A table or view opens to its columns with their types (a key marks the primary key); drag it into the editor
@@ -2217,11 +2245,11 @@ the SQL Workshop's connection may use. Without JavaScript the header's field ope
2217
2245
  remembered for the session; the history and the log keep the statement as written.
2218
2246
  - **Format** (**Ctrl + F7**) lays out the selection or the whole worksheet: a clause per line, the columns of a select
2219
2247
  list and the conditions of a WHERE one per line, joins and subqueries indented, keywords in lower or UPPER case
2220
- (chosen in the **?** menu). Strings, comments and `$$` bodies stay as they are. **Convert case** (**Ctrl + B**)
2248
+ (chosen in the **⋯** menu). Strings, comments and `$$` bodies stay as they are. **Convert case** (**Ctrl + B**)
2221
2249
  cycles the selection (or the word at the cursor) through UPPER, lower and Initial caps. **Find and replace**
2222
2250
  (**Ctrl + F**, **Ctrl + H**) with a regular expression or exact case, **Go to line** (**Ctrl + G**), and
2223
- **Ctrl + /** comments the selected lines out or in. The **?** menu lists every key; **F1** opens this help. The
2224
- upload button opens Load Data for the navigator's schema.
2251
+ **Ctrl + /** comments the selected lines out or in. The **⋯** menu lists every key; **F1** opens this help.
2252
+ *Load data* in the same menu opens Load Data for the navigator's schema.
2225
2253
  - Each statement commits on its own (**Autocommit**, like Database Actions). Statements run as the **owner connection** (an isolated workspace's
2226
2254
  developer login for its developers), on a connection of their own that is closed afterwards, so `SET ROLE` or
2227
2255
  `SET search_path` doesn't leak. **Run as** picks an application of your workspace instead: the statements then
@@ -2414,7 +2442,7 @@ the SQL Workshop's connection may use. Without JavaScript the header's field ope
2414
2442
  connections, memory, row security), the installed extensions and the schemas with their size.
2415
2443
  - **SQL Monitor** (administrators only): see [SQL Monitor](#sql-monitor) below (it replaced the Database Monitor;
2416
2444
  the old address `/builder/sql/utilities/monitor` redirects).
2417
- - **Database Users** (administrators only; Administration menu → *Database users*, `/builder/db-users`; sprint 99,
2445
+ - **Database Users** (administrators only; Administration → *Database users*, `/builder/db-users`; sprint 99,
2418
2446
  Database Actions: Database Users): see [Database users](#database-users).
2419
2447
  - **Schema Export / Import** (administrators only; `/builder/sql/data-pump`; sprint 103, Database Actions: Data
2420
2448
  Pump): see [Schema export and import](#schema-export-and-import).
@@ -525,7 +525,7 @@ navigation entries and application processes have the same `build_option` column
525
525
  | Table | Contents | Readable by the runtime role |
526
526
  |---|---|---|
527
527
  | `session` | Sessions: `token_hash` (SHA-256 of the cookie), `app_id` (NULL = builder), `username`, `roles` (resolved at sign-in), `csrf_token`, `state` (jsonb session state), `created_at`, `last_seen` | yes |
528
- | `activity_log` | `at`, `app_id`, `page_no`, `username`, `event` (`page_view`, `login`, `login_failed`, `login_locked`, `login_unlocked`, `logout`, `error`, `forbidden`, `api_token`, `password_expired`, `password_changed`; builder: `lock_broken`, `supporting_objects`, `sample_data`, …), `ip`, `elapsed_ms`, `detail` | yes (insert/select) |
528
+ | `activity_log` | `at`, `app_id`, `page_no`, `username`, `event` (`page_view`, `page_prefetch` (168: a page fetched for offline use), `login`, `login_failed`, `login_locked`, `login_unlocked`, `logout`, `error`, `forbidden`, `api_token`, `password_expired`, `password_changed`; builder: `lock_broken`, `supporting_objects`, `sample_data`, …), `ip`, `elapsed_ms`, `detail` | yes (insert/select) |
529
529
  | `debug_view` | [Debug messages](06-processing.md#debug-messages): one row per recorded request: `app_id`, `page_no`, `username`, `session_id`, `method`, `path` (without the query string), `status`, `level`, `started_at`, `elapsed_ms`, `entries`. Written through `meta.debug_save()` (runtime role only), not exported | no |
530
530
  | `debug_message` | The entries of a recorded request: `view_id`, `seq`, `elapsed_ms` (since the start), `duration_ms` (timed steps), `level`, `component`, `message` | no |
531
531
  | `web_request_log` | [Web requests from SQL](#web-requests-from-sql): `app_id`, `status`, the request (`url` or `source` + `params`, `method`, `headers`, `body`, `credential` name, `timeout_s`), `requested_by`, times, the response (`status_code`, `response_url`, `response_headers`, `response_body`), `message`. Kept 24 hours, not exported | no (through `meta.web_response`) |
@@ -13,6 +13,7 @@ the home screen with its own icon, full screen, and working on when the network
13
13
  | Installable | Adds a web app manifest and a service worker to the application. Phones offer "Add to home screen" / "Install"; the app then opens full screen, without the browser bar. (0.42) Where the browser offers the installation (Chrome, Edge), the header shows an **Install app** button (APEX: the navigation bar's Install App entry) |
14
14
  | Name under the icon | The short name on the home screen (the application's name when empty) |
15
15
  | Keep visited pages on the device | Pages a user opened are shown again when there is no connection (with a notice that they may be out of date) |
16
+ | Make every page of the navigation available offline | (168) With *Keep visited pages* on: after signing in, the device also fetches every page of the navigation menu and navigation bar the user may open, so the whole app works offline from the first visit; fetched again after *Fetch them again after* hours (1–168, default 12). [Below](#every-page-offline) |
16
17
  | Keep forms sent without a connection | Forms submitted offline are kept on the device and sent when the connection is back |
17
18
  | Push notifications | Users may turn on notifications per device; application code sends them ([below](#push-notifications)) |
18
19
  | Icon | A square PNG of at least 512 × 512 pixels. Without one, the app gets a tile with its initial in the theme's accent colour |
@@ -35,9 +36,38 @@ Everything is per application, under its own address: `/a/<alias>/manifest.webma
35
36
  listing the pages that are on the device.
36
37
  - Kept pages contain the user's data. They stay on the device only while the user is signed in:
37
38
  signing in or out removes them, so the next person on a shared device doesn't see them.
38
- - Live data (reports, charts) is as fresh as the last visit. Actions that need the server (searching,
39
+ - Live data (reports, charts) is as fresh as the last visit (or the last fetch, below). Actions that need the server (searching,
39
40
  downloads, dynamic actions) wait for the connection.
40
41
 
42
+ ## Every page offline
43
+
44
+ (168) With *Make every page of the navigation available offline*, a user doesn't have to open every page
45
+ once before going out of range:
46
+
47
+ 1. On the first page after signing in (and on the first page opened on a device), the page hands the
48
+ links of its **navigation menu and navigation bar** to the service worker. Those are exactly the pages
49
+ the server showed this user: authorization schemes, conditions and page access already applied. Dialog
50
+ pages, other sites and the sign-in and sign-out pages are left out; at most 60 pages are fetched.
51
+ 2. The service worker fetches them **one at a time** in the background, with a short pause between
52
+ them, and keeps each page that comes back. It skips the whole thing when the browser asks to save
53
+ data (*Data Saver*), and stops at the first network error, to try again on the next page.
54
+ 3. When it is done it notes the time; after *Fetch them again after* hours (default 12) the next page
55
+ opened online starts a new round. Signing in or out empties the kept pages, so the next person
56
+ starts over.
57
+
58
+ The server shows a fetched page **read-only** (the request carries `X-Pgkiln-Prefetch: 1`):
59
+
60
+ - the page's transaction is read-only: an application process or a region query that writes fails
61
+ (the region shows its error in the kept copy) and nothing is changed;
62
+ - the session's state is not kept, and a message waiting for the next page stays there;
63
+ - a page with a process that runs when it opens (point *On load*) is not fetched at all: it is kept
64
+ only when the user opens it, as before. So are dialogs, documents and downloads;
65
+ - each fetch is logged as `page_prefetch`, apart from `page_view`, so activity reports count real visits.
66
+
67
+ Only the pages of the menus are fetched, with their first page of rows: a form for one record
68
+ (`?P3_ID=…`) or a report's other pages are kept only once the user opens them. The kept copies hold
69
+ the user's data, like visited pages: turn it on for apps used on devices that belong to one person.
70
+
41
71
  ## Forms sent offline
42
72
 
43
73
  With *Keep forms sent without a connection* on, a form submitted while the network is down (or
@@ -127,9 +157,10 @@ results, and a *Send test* button.
127
157
 
128
158
  ## Example
129
159
 
130
- The HR example application is a PWA: install it from the browser menu on a phone, open a few pages,
131
- switch on airplane mode, and file a leave request; it is sent when the connection is back. Its
132
- employee form records a work location and takes the photo with the camera (`examples/hr/hr_12_pwa.sql`).
160
+ The HR example application is a PWA: install it from the browser menu on a phone, sign in, wait a few
161
+ seconds, switch on airplane mode, and every page of the menu still opens (`examples/hr/hr_82_pwa_prefetch.sql`);
162
+ file a leave request, and it is sent when the connection is back. Its employee form records a work location
163
+ and takes the photo with the camera (`examples/hr/hr_12_pwa.sql`).
133
164
 
134
165
  ## Not included
135
166
 
@@ -0,0 +1,11 @@
1
+ -- =====================================================================
2
+ -- HR example, part 82: the whole app offline from the first visit
3
+ -- (docs/guide/17-mobile.md, sprint 168)
4
+ --
5
+ -- After signing in on a phone, every page of the navigation menu the
6
+ -- user may open is fetched in the background and kept on the device, so
7
+ -- the app works on the train even for pages never opened before; the
8
+ -- copies are fetched again after 12 hours. Pages that run processes when
9
+ -- they open (20, 35, 43) are kept only when the user opens them.
10
+ -- =====================================================================
11
+ update meta.app set pwa_prefetch_hours = 12 where alias = 'hr';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgkiln",
3
- "version": "0.87.0",
3
+ "version": "0.89.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.js CHANGED
@@ -2972,6 +2972,17 @@ const liveBadges = (() => {
2972
2972
  if (e.data && e.data.type === 'pgkiln:pushed') liveBadges.now();
2973
2973
  });
2974
2974
  if (new URLSearchParams(location.search).get('queued') === '1') note(t('pwa.queued'));
2975
+ // (168) every page of the navigation offline: this page and the menu's and navigation bar's pages of the app
2976
+ // (the server already left out the ones this user may not open; dialogs and other sites are not pages to keep)
2977
+ if (body.dataset.prefetch && navigator.onLine && !(navigator.connection && navigator.connection.saveData)) {
2978
+ const links = [...document.querySelectorAll('#t-nav a[href], .t-navbar a[href]')].filter((a) => !a.hasAttribute('data-dialog'));
2979
+ const urls = [location.href, ...links.map((a) => a.href)].filter((h) => {
2980
+ const u = new URL(h, location.href);
2981
+ return u.origin === location.origin && u.pathname.startsWith(`${body.dataset.base}/`) && !/\/(login|logout)$/.test(u.pathname);
2982
+ });
2983
+ // a page without the navigation (the sign-in page, a dialog) has nothing to hand over
2984
+ if (links.length) post({ type: 'pgkiln:prefetch', urls: [...new Set(urls)] });
2985
+ }
2975
2986
  }
2976
2987
 
2977
2988
  // (165) live badges: every data-live seconds the page asks for its badges (navigation menu and bar,
@@ -444,7 +444,9 @@ body.ide {
444
444
  .pd > .tab-panel > .pd-pane-title { display: none; }
445
445
  /* with script the designer fills the window and each pane scrolls on its own */
446
446
  .js .ide-full .ide-main { height: 100vh; height: 100dvh; display: flex; flex-direction: column; }
447
- .js .ide-full .ide-work { flex: 1 1 auto; min-height: 0; }
447
+ /* (UX-2) the work area is the containing block and clips: nothing inside (a screen-reader heading placed
448
+ absolutely, a pane's overflow) can make the page itself scroll */
449
+ .js .ide-full .ide-work { flex: 1 1 auto; min-height: 0; position: relative; overflow: hidden; }
448
450
  .js .ide-full .pd { height: 100%; min-height: 0; }
449
451
  .js .pd-pane { min-height: 0; overflow: auto; }
450
452
  .js .pd-center { display: flex; flex-direction: column; overflow: hidden; }
@@ -484,8 +486,9 @@ body.ide {
484
486
  }
485
487
  .pd-node .icon { width: 16px; height: 16px; color: var(--muted); }
486
488
  a.pd-node:hover { background: var(--surface-3); }
487
- .pd-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
488
- .pd-note { margin-left: auto; padding-left: 8px; max-width: 45%; overflow: hidden; text-overflow: ellipsis; color: var(--muted); font-size: 11.5px; }
489
+ /* (UX-12) the name keeps its room: the type gives way first (the icon tells it, the tooltip names it) */
490
+ .pd-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; flex: 0 1 auto; }
491
+ .pd-note { margin-left: auto; padding-left: 8px; max-width: 45%; min-width: 0; flex: 0 1000 auto; overflow: hidden; text-overflow: clip; white-space: nowrap; color: var(--muted); font-size: 11.5px; }
489
492
  .pd-folder { color: var(--muted); font-weight: 600; cursor: default; }
490
493
  .pd-tag { display: inline-flex; }
491
494
  .pd-tag .icon { width: 13px; height: 13px; color: var(--warn); }
@@ -1671,3 +1674,54 @@ a.cp-type:hover, a.cp-type:focus-visible { border-color: var(--accent); }
1671
1674
  .api-client-actions select { width: auto; min-height: 0; padding-block: 0.2rem; font-size: 0.8125rem; }
1672
1675
  .api-client-actions .inline-form { display: flex; gap: 6px; align-items: center; }
1673
1676
  .api-clients td.actions { text-align: end; }
1677
+
1678
+ /* (UX-2) Page Designer on a short window: the gallery folded to its tabs (builder.js), Save at hand */
1679
+ @media (min-width: 1024px) {
1680
+ .js .pd-gallery.is-folded { height: auto; min-height: 0; overflow: hidden; }
1681
+ .js .pd-gallery.is-folded .tab-panel { display: none; }
1682
+ }
1683
+ .tb-save.is-dirty { position: relative; }
1684
+ .tb-save.is-dirty::after { content: ""; position: absolute; top: 3px; right: 3px; width: 7px; height: 7px; border-radius: 50%; background: var(--hot); }
1685
+ #pd-p-props .component-form > .buttons { position: sticky; bottom: 0; z-index: 1; margin: 0; padding: 8px 0; background: var(--surface); border-top: 1px solid var(--border); }
1686
+ .pd-note.is-cut { display: none; }
1687
+
1688
+ /* (UX-3) the rail with labels next to its icons (the toggle at its bottom, kept in the session) */
1689
+ .rail-toggle { width: 100%; border: 0; background: none; cursor: pointer; font: inherit; }
1690
+ @media (min-width: 900px) {
1691
+ .ide.rail-labels { --rail-w: 196px; }
1692
+ .rail-labels .rail-list { padding: 0 10px; }
1693
+ .rail-labels .rail-link { display: flex; justify-content: flex-start; gap: 12px; padding: 0 12px; }
1694
+ .rail-labels .rail-link .rail-label { position: static; opacity: 1; transform: none; padding: 0; background: none; color: inherit;
1695
+ box-shadow: none; font-size: 13.5px; font-weight: 500; pointer-events: auto; }
1696
+ .rail-labels .rail-logo { margin-left: 18px; }
1697
+ .rail-labels .rail-user { justify-content: flex-start; padding-left: 14px; }
1698
+ }
1699
+
1700
+ /* (UX-4) Administration: the workspace utilities in sections of compact tiles, with a filter (builder.js) */
1701
+ .adm { max-width: 1180px; }
1702
+ .adm .title-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; }
1703
+ .adm-filter input { width: min(320px, 100%); }
1704
+ .adm-lede { max-width: 75ch; }
1705
+ .adm-section { margin-top: 22px; }
1706
+ .adm-section h2 { font-size: 15px; margin: 0 0 8px; color: var(--text-2, var(--muted)); text-transform: none; }
1707
+ .adm-tiles { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr)); gap: 8px; }
1708
+ .adm-tile { display: flex; flex-direction: column; gap: 2px; height: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius);
1709
+ background: var(--surface); color: var(--text); text-decoration: none; transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease); }
1710
+ .adm-tile:hover, .adm-tile:focus-visible { border-color: var(--hot); background: var(--surface-2); }
1711
+ .adm-tile-head { display: flex; align-items: center; gap: 8px; }
1712
+ .adm-tile-head .icon { width: 16px; height: 16px; color: var(--hot); flex: none; }
1713
+ .adm-tile-text { color: var(--muted); font-size: 12.5px; line-height: 1.4; }
1714
+ .adm-reports { columns: 2 260px; }
1715
+
1716
+ /* (UX-5) a builder page's header: one heading, one primary (accent) action, secondary actions and a ⋯ menu */
1717
+ .page-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 16px; margin: 0 0 16px; }
1718
+ .page-head h1 { margin: 0; font-size: 22px; }
1719
+ .page-head-sub { margin: 2px 0 0; }
1720
+ .page-head-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
1721
+ .page-head-actions .btn { display: inline-flex; align-items: center; gap: 6px; }
1722
+ .page-head-actions .icon { width: 15px; height: 15px; }
1723
+ /* (UX-13) the Workspace home's "Needs attention" list for administrators, above the tools */
1724
+ .wh-attention { display: block; margin: 0 0 16px; }
1725
+ .wh-attention h2 { margin: 0 0 4px; font-size: 14px; }
1726
+ .wh-attention ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
1727
+ .wh-attention .icon { width: 15px; height: 15px; vertical-align: -2px; }
package/public/builder.js CHANGED
@@ -508,6 +508,38 @@
508
508
  const dirty = () => save.classList.add('is-dirty');
509
509
  form.addEventListener('input', dirty);
510
510
  form.addEventListener('change', dirty);
511
+ // (UX-2) Ctrl+S saves; leaving with unsaved changes asks first (saving doesn't)
512
+ let submitting = false;
513
+ form.addEventListener('submit', () => (submitting = true));
514
+ document.addEventListener('keydown', (e) => {
515
+ if ((e.ctrlKey || e.metaKey) && !e.altKey && e.key.toLowerCase() === 's' && !e.target.closest?.('.ws, dialog')) {
516
+ e.preventDefault();
517
+ form.requestSubmit();
518
+ }
519
+ });
520
+ window.addEventListener('beforeunload', (e) => {
521
+ if (!submitting && save.classList.contains('is-dirty')) {
522
+ e.preventDefault();
523
+ e.returnValue = '';
524
+ }
525
+ });
526
+ }
527
+ // (UX-2) on a short window the gallery is only its tabs: choosing a tab opens it, choosing the open one folds it
528
+ const gallery = document.querySelector('.pd-gallery');
529
+ if (gallery) {
530
+ const short = window.matchMedia('(max-height: 800px)');
531
+ const apply = () => gallery.classList.toggle('is-folded', short.matches && store.get('gallery') !== 'open');
532
+ apply();
533
+ short.addEventListener('change', apply);
534
+ gallery.addEventListener('click', (e) => {
535
+ const t = e.target.closest('[role=tab]');
536
+ if (!t || !short.matches) return;
537
+ const folded = gallery.classList.contains('is-folded');
538
+ const wasActive = t.getAttribute('aria-selected') === 'true';
539
+ const open = folded || !wasActive;
540
+ store.set('gallery', open ? 'open' : null);
541
+ gallery.classList.toggle('is-folded', !open);
542
+ }, true); // before the tabs switch, so the tab that was open is known
511
543
  }
512
544
  }
513
545
 
@@ -1011,7 +1043,41 @@
1011
1043
  setupQueryCanvas();
1012
1044
  setupThemePreview();
1013
1045
  setupTabs();
1046
+ // (UX-4) Administration: filter the utilities by their text; sections without a match hide
1047
+ const admQ = document.querySelector('[data-adm-filter]');
1048
+ if (admQ) {
1049
+ admQ.closest('.adm-filter').hidden = false;
1050
+ const page = admQ.closest('.adm');
1051
+ const none = page.querySelector('.adm-none');
1052
+ admQ.addEventListener('input', () => {
1053
+ const q = admQ.value.trim().toLowerCase();
1054
+ let any = false;
1055
+ for (const sec of page.querySelectorAll('.adm-section')) {
1056
+ let shown = 0;
1057
+ for (const li of sec.querySelectorAll('li')) {
1058
+ const hit = !q || li.textContent.toLowerCase().includes(q);
1059
+ li.hidden = !hit;
1060
+ if (hit) shown++;
1061
+ }
1062
+ sec.hidden = !shown;
1063
+ if (shown) any = true;
1064
+ }
1065
+ none.hidden = any;
1066
+ });
1067
+ }
1014
1068
  for (const tree of document.querySelectorAll('.pd-tree')) setupTree(tree);
1069
+ // (UX-12) a node's type text only when it fits whole: the name comes first (the icon and tooltip tell the type)
1070
+ const fitNotes = () => {
1071
+ for (const n of document.querySelectorAll('.pd-tree .pd-note')) {
1072
+ n.classList.remove('is-cut');
1073
+ if (n.scrollWidth > n.clientWidth + 1) n.classList.add('is-cut');
1074
+ }
1075
+ };
1076
+ fitNotes();
1077
+ if ('ResizeObserver' in window) {
1078
+ const pane = document.querySelector('.pd-tree')?.closest('.pd-pane');
1079
+ if (pane) new ResizeObserver(() => requestAnimationFrame(fitNotes)).observe(pane);
1080
+ }
1015
1081
  for (const tree of document.querySelectorAll('.pd-tree[data-tree-menu]')) setupTreeMenu(tree);
1016
1082
  setupProperties();
1017
1083
  setupCanvas();
package/public/palette.js CHANGED
@@ -18,15 +18,22 @@
18
18
  if (/^\/builder\/sql\/./.test(path) && !/\/(panel|navigator|action)$/.test(path)) {
19
19
  const title = document.title.replace(/ · Builder$/, '');
20
20
  const href = path + location.search;
21
- const list = (store.get('sw.recent') || []).filter((r) => r && r.href !== href);
21
+ // (UX-14) one entry per page (its path): opened again with other parameters, the newest wins
22
+ const list = (store.get('sw.recent') || []).filter((r) => r && typeof r.href === 'string' && r.href.split('?')[0] !== path);
22
23
  list.unshift({ href, title });
23
24
  store.set('sw.recent', list.slice(0, 8));
24
25
  }
25
26
  const recent = document.querySelector('[data-lp-recent]');
26
27
  if (recent) {
27
28
  const list = store.get('sw.recent') || [];
29
+ // (UX-14) not the page you are on, not what is pinned already, each path once
30
+ const pinned = new Set([...document.querySelectorAll('.lp-pins .lp-chips:not([data-lp-recent]) a[href]')].map((a) => a.getAttribute('href').split('?')[0]));
31
+ const seen = new Set([path]);
28
32
  for (const r of list) {
29
33
  if (typeof r?.href !== 'string' || !r.href.startsWith('/builder/')) continue;
34
+ const p = r.href.split('?')[0];
35
+ if (seen.has(p) || pinned.has(p)) continue;
36
+ seen.add(p);
30
37
  const li = document.createElement('li');
31
38
  const a = document.createElement('a');
32
39
  a.href = r.href;