pgkiln 0.83.0 → 0.84.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.
- package/db/migrations/154_notifications.sql +186 -0
- package/docs/apex-feature-parity.md +2 -1
- package/docs/guide/03-builder.md +2 -0
- package/docs/guide/04-pages-and-regions.md +42 -0
- package/docs/guide/06-processing.md +22 -1
- package/docs/guide/07-dynamic-actions.md +1 -0
- package/docs/guide/09-reference.md +15 -0
- package/docs/guide/11-from-apex.md +1 -0
- package/docs/guide/12-development.md +7 -0
- package/package.json +1 -1
- package/public/app.css +29 -0
- package/public/app.js +78 -0
- package/public/sw.js +5 -1
- package/src/app.ts +2 -0
- package/src/builder/code-editor.ts +1 -1
- package/src/builder/components.ts +10 -6
- package/src/builder/i18n/de.ts +8 -0
- package/src/builder/i18n/en.ts +8 -0
- package/src/builder/i18n/es.ts +8 -0
- package/src/builder/i18n/fr.ts +8 -0
- package/src/builder/i18n/it.ts +8 -0
- package/src/builder/i18n/ja.ts +8 -0
- package/src/builder/i18n/ko.ts +8 -0
- package/src/builder/i18n/nl.ts +8 -0
- package/src/builder/i18n/pt.ts +8 -0
- package/src/builder/i18n/zh-cn.ts +8 -0
- package/src/builder/i18n/zh-tw.ts +8 -0
- package/src/builder/notification-settings.ts +27 -0
- package/src/builder/routes.ts +3 -0
- package/src/cli/replace.ts +2 -0
- package/src/i18n/ar.ts +9 -0
- package/src/i18n/cs.ts +9 -0
- package/src/i18n/da.ts +9 -0
- package/src/i18n/de.ts +9 -0
- package/src/i18n/el.ts +9 -0
- package/src/i18n/es.ts +9 -0
- package/src/i18n/fi.ts +9 -0
- package/src/i18n/fr.ts +9 -0
- package/src/i18n/he.ts +9 -0
- package/src/i18n/it.ts +9 -0
- package/src/i18n/ja.ts +9 -0
- package/src/i18n/ko.ts +9 -0
- package/src/i18n/nb.ts +9 -0
- package/src/i18n/pl.ts +9 -0
- package/src/i18n/pt.ts +9 -0
- package/src/i18n/ru.ts +9 -0
- package/src/i18n/sv.ts +9 -0
- package/src/i18n/tr.ts +9 -0
- package/src/i18n/uk.ts +9 -0
- package/src/i18n/zh.ts +9 -0
- package/src/i18n.ts +18 -0
- package/src/metadata.ts +9 -3
- package/src/runtime/engine.ts +2 -1
- package/src/runtime/feedback.ts +1 -1
- package/src/runtime/lists.ts +43 -11
- package/src/runtime/notifications.ts +237 -0
- package/src/runtime/processes.ts +45 -1
- package/src/runtime/regions.ts +11 -15
- package/src/runtime/render.ts +12 -3
- package/src/runtime/routes.ts +12 -4
- package/src/runtime/signin.ts +2 -1
- package/src/session.ts +13 -4
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- 154: in-app notifications, list entry badges from SQL, live badges
|
|
3
|
+
-- (APEX: navigation menu badges, a notification bell in the header,
|
|
4
|
+
-- badges refreshed by dynamic actions and JavaScript)
|
|
5
|
+
--
|
|
6
|
+
-- meta.notification keeps notifications for the users of an application.
|
|
7
|
+
-- Application code adds them with meta.notify(user, title, body, page,
|
|
8
|
+
-- items, icon, push) or the declarative process send_notification. With
|
|
9
|
+
-- meta.app.notifications on, signed-in users get a bell in the header: the
|
|
10
|
+
-- number of unread notifications and the newest ones; opening one marks it
|
|
11
|
+
-- read and goes to its page (a link signed for the recipient, as push
|
|
12
|
+
-- notifications are). The page /a/<alias>/notifications lists them all.
|
|
13
|
+
--
|
|
14
|
+
-- meta.list_entry.badge_query: a SELECT whose first value is the entry's
|
|
15
|
+
-- badge (as meta.button.badge_query, 029), run as the application's role.
|
|
16
|
+
--
|
|
17
|
+
-- meta.app.live_badges_s: every so many seconds (10 to 3600) the open page
|
|
18
|
+
-- asks the server for its badges again (navigation menu and bar, buttons,
|
|
19
|
+
-- the bell) and updates them in place; not while the tab is hidden.
|
|
20
|
+
--
|
|
21
|
+
-- The dynamic action event timer runs actions every so many seconds (the
|
|
22
|
+
-- trigger: a number of seconds, 5 to 3600), e.g. refresh_region.
|
|
23
|
+
--
|
|
24
|
+
-- Notifications are installation data, not part of the application's export.
|
|
25
|
+
-- A user keeps the newest 500; read ones go after 90 days.
|
|
26
|
+
-- =====================================================================
|
|
27
|
+
|
|
28
|
+
alter table meta.app add column notifications boolean not null default false;
|
|
29
|
+
comment on column meta.app.notifications is 'In-app notifications (154): a bell in the header for signed-in users (meta.notify, send_notification)';
|
|
30
|
+
alter table meta.app add column live_badges_s int check (live_badges_s between 10 and 3600);
|
|
31
|
+
comment on column meta.app.live_badges_s is 'Live badges (154): the page refreshes its badges and the bell every so many seconds (null: never)';
|
|
32
|
+
|
|
33
|
+
-- an export made before 154 has no notifications key: import_app inserts NULL (see 054)
|
|
34
|
+
create function meta.app_notifications_default() returns trigger
|
|
35
|
+
language plpgsql set search_path = pg_catalog as $$
|
|
36
|
+
begin
|
|
37
|
+
new.notifications := coalesce(new.notifications, false);
|
|
38
|
+
return new;
|
|
39
|
+
end $$;
|
|
40
|
+
create trigger app_notifications_default before insert or update on meta.app
|
|
41
|
+
for each row execute function meta.app_notifications_default();
|
|
42
|
+
|
|
43
|
+
alter table meta.list_entry add column badge_query text;
|
|
44
|
+
comment on column meta.list_entry.badge_query is 'A SELECT whose first value is the badge (154); wins over badge';
|
|
45
|
+
|
|
46
|
+
alter table meta.process drop constraint process_type_check;
|
|
47
|
+
alter table meta.process add constraint process_type_check check (type in (
|
|
48
|
+
'ai_generate', 'chain', 'data_load', 'download', 'form_dml', 'grid_dml', 'invoke_api', 'sql', 'workflow', 'plugin', 'send_push', 'send_email',
|
|
49
|
+
'close_dialog', 'clear_session_state', 'reset_pagination', 'user_preferences', 'server_geocoding', 'print_report',
|
|
50
|
+
'human_task_create', 'human_task_manage', 'form_init', 'send_notification'));
|
|
51
|
+
|
|
52
|
+
alter table meta.dynamic_action drop constraint dynamic_action_event_check;
|
|
53
|
+
alter table meta.dynamic_action add constraint dynamic_action_event_check check (event in (
|
|
54
|
+
'change', 'click', 'load', 'dialog_closed', 'after_refresh', 'selection_change', 'focus', 'blur', 'key_release', 'before_submit', 'custom', 'menu_entry',
|
|
55
|
+
'double_click', 'key_down', 'key_press', 'mouse_enter', 'mouse_leave', 'select', 'resize', 'scroll', 'page_unload', 'before_refresh', 'facets_change',
|
|
56
|
+
'grid_row_init', 'grid_save', 'calendar_date_select', 'calendar_view_change', 'map_object_click', 'map_change', 'tree_selection_change', 'timer'));
|
|
57
|
+
|
|
58
|
+
create table meta.notification (
|
|
59
|
+
id bigserial primary key,
|
|
60
|
+
app_id int not null references meta.app on delete cascade,
|
|
61
|
+
username text not null check (username ~ '^[^\s:]{1,100}$'),
|
|
62
|
+
title text not null check (length(title) between 1 and 200),
|
|
63
|
+
body text check (length(body) <= 1000),
|
|
64
|
+
-- a page of the application with items, signed for the recipient (as meta.send_push)
|
|
65
|
+
url text check (url ~ '^/a/[a-z0-9_-]+/[0-9]+(\?[^\s<>"''`\\]*)?$'),
|
|
66
|
+
icon text check (icon ~ '^[a-z0-9-]{1,40}$'),
|
|
67
|
+
created_by text,
|
|
68
|
+
created_at timestamptz not null default now(),
|
|
69
|
+
read_at timestamptz
|
|
70
|
+
);
|
|
71
|
+
create index notification_user on meta.notification (app_id, lower(username), id desc);
|
|
72
|
+
create index notification_unread on meta.notification (app_id, lower(username)) where read_at is null;
|
|
73
|
+
revoke all on meta.notification from public;
|
|
74
|
+
-- the runtime shows a user their own and marks them read (src/runtime/notifications.ts)
|
|
75
|
+
grant select, update (read_at), delete on meta.notification to pgkiln_runtime;
|
|
76
|
+
comment on table meta.notification is 'In-app notifications of application users (154): meta.notify; not exported';
|
|
77
|
+
|
|
78
|
+
-- (090) an isolated workspace's logins see their own applications' rows only
|
|
79
|
+
alter table meta.notification enable row level security;
|
|
80
|
+
create policy workspace on meta.notification
|
|
81
|
+
using ((select meta.isolation_off()) or app_id in (select meta.visible_apps()))
|
|
82
|
+
with check ((select meta.isolation_off()) or app_id in (select meta.visible_apps()));
|
|
83
|
+
|
|
84
|
+
-- ---------------------------------------------------------------- the API
|
|
85
|
+
|
|
86
|
+
-- Add a notification for p_user of the current application; returns its id.
|
|
87
|
+
-- p_page/p_items: the page it opens (signed for p_user). p_icon: an icon name.
|
|
88
|
+
-- p_push: also send it to the user's devices when push notifications are on.
|
|
89
|
+
create function meta.notify(
|
|
90
|
+
p_user text,
|
|
91
|
+
p_title text,
|
|
92
|
+
p_body text default null,
|
|
93
|
+
p_page int default null,
|
|
94
|
+
p_items jsonb default '{}',
|
|
95
|
+
p_icon text default null,
|
|
96
|
+
p_push boolean default false
|
|
97
|
+
) returns bigint
|
|
98
|
+
language plpgsql volatile security definer set search_path = meta, pg_catalog as $$
|
|
99
|
+
declare
|
|
100
|
+
v_app int := meta.app_id();
|
|
101
|
+
v_alias text;
|
|
102
|
+
v_on boolean;
|
|
103
|
+
v_push boolean;
|
|
104
|
+
v_url text;
|
|
105
|
+
v_id bigint;
|
|
106
|
+
begin
|
|
107
|
+
if v_app is null then
|
|
108
|
+
raise exception 'meta.notify: no current application (call it from application code)';
|
|
109
|
+
end if;
|
|
110
|
+
select alias, notifications, pwa and pwa_push into v_alias, v_on, v_push from meta.app where id = v_app;
|
|
111
|
+
if not coalesce(v_on, false) then
|
|
112
|
+
raise exception 'meta.notify: notifications are off for this application (Settings → User Interface → Notifications)';
|
|
113
|
+
end if;
|
|
114
|
+
if p_user is null or p_user !~ '^[^\s:]{1,100}$' then
|
|
115
|
+
raise exception 'meta.notify: the user name is required (at most 100 characters, no spaces)';
|
|
116
|
+
end if;
|
|
117
|
+
if p_title is null or length(btrim(p_title)) not between 1 and 200 then
|
|
118
|
+
raise exception 'meta.notify: the title is required (at most 200 characters)';
|
|
119
|
+
end if;
|
|
120
|
+
if length(p_body) > 1000 then
|
|
121
|
+
raise exception 'meta.notify: the body has at most 1000 characters';
|
|
122
|
+
end if;
|
|
123
|
+
if p_icon is not null and p_icon !~ '^[a-z0-9-]{1,40}$' then
|
|
124
|
+
raise exception 'meta.notify: the icon is an icon name (lower case letters, digits and -)';
|
|
125
|
+
end if;
|
|
126
|
+
if p_items is not null and p_items <> '{}'::jsonb and (jsonb_typeof(p_items) <> 'object' or p_page is null) then
|
|
127
|
+
raise exception 'meta.notify: the items are a JSON object of item names and values, with a page';
|
|
128
|
+
end if;
|
|
129
|
+
if p_page is not null then
|
|
130
|
+
if not exists (select 1 from meta.page where app_id = v_app and page_no = p_page) then
|
|
131
|
+
raise exception 'meta.notify: page % does not exist in this application', p_page;
|
|
132
|
+
end if;
|
|
133
|
+
v_url := format('/a/%s/%s', v_alias, p_page)
|
|
134
|
+
|| case when coalesce(p_items, '{}'::jsonb) = '{}'::jsonb then '' else
|
|
135
|
+
'?' || (select string_agg(upper(key) || '=' || meta.url_encode(coalesce(value #>> '{}', '')), '&' order by upper(key) collate "C")
|
|
136
|
+
from jsonb_each(p_items))
|
|
137
|
+
|| '&cs=' || meta.url_checksum(v_app, p_page, p_user, p_items) end;
|
|
138
|
+
end if;
|
|
139
|
+
insert into meta.notification (app_id, username, title, body, url, icon, created_by)
|
|
140
|
+
values (v_app, p_user, btrim(p_title), nullif(p_body, ''), v_url, p_icon, meta.app_user())
|
|
141
|
+
returning id into v_id;
|
|
142
|
+
-- a user keeps the newest 500
|
|
143
|
+
delete from meta.notification where id in (
|
|
144
|
+
select id from meta.notification where app_id = v_app and lower(username) = lower(p_user) order by id desc offset 500);
|
|
145
|
+
-- now and then: read notifications older than 90 days
|
|
146
|
+
if random() < 0.02 then
|
|
147
|
+
delete from meta.notification where app_id = v_app and read_at < now() - interval '90 days';
|
|
148
|
+
end if;
|
|
149
|
+
if p_push and coalesce(v_push, false) then
|
|
150
|
+
perform meta.send_push(p_user, btrim(p_title), nullif(p_body, ''), p_page, coalesce(p_items, '{}'::jsonb), 'n' || v_id);
|
|
151
|
+
end if;
|
|
152
|
+
return v_id;
|
|
153
|
+
end
|
|
154
|
+
$$;
|
|
155
|
+
|
|
156
|
+
-- The number of unread notifications of p_user (default: the current user), e.g. as a badge_query.
|
|
157
|
+
create function meta.unread_notifications(p_user text default null) returns int
|
|
158
|
+
language sql stable security definer set search_path = meta, pg_catalog as $$
|
|
159
|
+
select count(*)::int from meta.notification
|
|
160
|
+
where app_id = meta.app_id() and lower(username) = lower(coalesce(p_user, meta.app_user())) and read_at is null
|
|
161
|
+
$$;
|
|
162
|
+
|
|
163
|
+
-- Mark the current user's notification p_id read, or all of them (p_id null); returns how many.
|
|
164
|
+
create function meta.mark_notifications_read(p_id bigint default null) returns int
|
|
165
|
+
language plpgsql volatile security definer set search_path = meta, pg_catalog as $$
|
|
166
|
+
declare
|
|
167
|
+
n int;
|
|
168
|
+
begin
|
|
169
|
+
update meta.notification set read_at = now()
|
|
170
|
+
where app_id = meta.app_id() and lower(username) = lower(meta.app_user()) and read_at is null
|
|
171
|
+
and (p_id is null or id = p_id);
|
|
172
|
+
get diagnostics n = row_count;
|
|
173
|
+
return n;
|
|
174
|
+
end
|
|
175
|
+
$$;
|
|
176
|
+
|
|
177
|
+
-- The current user's notifications in the current application (for a report of your own).
|
|
178
|
+
create view meta.my_notifications with (security_barrier) as
|
|
179
|
+
select id, title, body, url, icon, created_at, read_at, read_at is null as unread
|
|
180
|
+
from meta.notification
|
|
181
|
+
where app_id = meta.app_id() and lower(username) = lower(meta.app_user());
|
|
182
|
+
comment on view meta.my_notifications is 'The current user''s notifications in the current application (154), newest first by id';
|
|
183
|
+
|
|
184
|
+
grant execute on function meta.notify(text, text, text, int, jsonb, text, boolean), meta.unread_notifications(text),
|
|
185
|
+
meta.mark_notifications_read(bigint) to public;
|
|
186
|
+
grant select on meta.my_notifications to public;
|
|
@@ -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 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 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
|
|
|
@@ -84,6 +84,7 @@ database links row and added static workspace files with workspace web credentia
|
|
|
84
84
|
| Breadcrumb | ✅ | Automatic, from the page's breadcrumb parent; (sprint 140) also a `breadcrumb` region for the Breadcrumb bar position (the title bar then leaves its own out), with or without the current page. No separate breadcrumb shared component: the trail follows the pages' parents |
|
|
85
85
|
| Help Text region, page help | ✅ | (sprint 140) The page's *Help text* (Markdown with `&ITEM.` substitutions, item values stay text) and a `help_text` region that shows it ([chapter 4](guide/04-pages-and-regions.md#breadcrumb-and-help_text)) |
|
|
86
86
|
| Navigation menu (side or top) | ✅ | Collapsible side menu or top bar; a drawer on tablets and phones |
|
|
87
|
+
| Navigation menu badges, notification bell, live-updating badges | ✅ | (sprint 165) A list entry's badge or badge query in the navigation menu and bar; Settings → User Interface → *Notification bell* (unread count, newest notifications, Mark all read, the page /a/<alias>/notifications) fed by `meta.notify` or the process type `send_notification`; *Live badges* refresh the menu, bar, button and bell badges in place every so many seconds; the dynamic action event `timer` ([chapter 4](guide/04-pages-and-regions.md#notifications-and-live-badges)) |
|
|
87
88
|
| Region display selector, tabs | ✅ | Tabs or a select list; regions can share a tab, *Show all*, the choice remembered in the session, `#R<id>` links. Accessible tabs with JavaScript; without it every region shows with links to each |
|
|
88
89
|
| Tree | ✅ | `tree` region from id / parent id / label rows, with icons, links and the first levels open; works without JavaScript |
|
|
89
90
|
| Map region (26.1: vector tiles, bounding box) | ✅ | `map` region: several layers per map, each with its own query (markers, lines and areas from GeoJSON or a PostGIS geometry, heat maps), a legend that switches layers on and off and a colour per layer; marker clustering; popups with links. A report can be filtered by the visible area or by the distance from the map's centre on the server: with PostGIS (`ST_Intersects`/`ST_DWithin`, detected automatically) when installed, otherwise on latitude/longitude (bounding box, haversine). Configurable tile server. (0.31) **Large data sets**: a layer loads only the places in the visible area, again as the map moves (`visible_area`), or is served as **Mapbox Vector Tiles** (`tiles`, MVT 2.1, drawn on canvases with popups; readable by MapLibre, OpenLayers or QGIS), both filtered on the server like the report filter ([chapter 4](guide/04-pages-and-regions.md#map)). (sprint 159) Layer types **Lines**, **Polygons** and **Extruded Polygons** with fill and line colours, width and opacity per layer or per row; extruded polygons drawn flat, shaded by height (no 3D view) |
|
package/docs/guide/03-builder.md
CHANGED
|
@@ -1605,6 +1605,8 @@ accounts or custom).
|
|
|
1605
1605
|
look until 0.28; see [chapter 14](14-globalization.md#base-styles-iris-and-standard)), accent colour, header colour, *side* or *top* navigation (on tablets and phones the
|
|
1606
1606
|
menu is always a drawer), a [list](04-pages-and-regions.md#list-lists) as the navigation menu
|
|
1607
1607
|
(instead of the navigation entries) and as the navigation bar (links in the header), the theme style (automatic, light or dark) and whether users may choose light or dark.
|
|
1608
|
+
(sprint 165) **Notifications**: the notification bell in the header and **live badges** (every so many seconds an
|
|
1609
|
+
open page refreshes its badges); see [Notifications and live badges](04-pages-and-regions.md#notifications-and-live-badges).
|
|
1608
1610
|
**Theme Roller** opens the [style variants](14-globalization.md#style-variants-theme-roller): several
|
|
1609
1611
|
saved styles (colours, font, font size, corners), the default one, and whether users may choose.
|
|
1610
1612
|
- **Globalization**: primary language, translated languages, how the language is chosen, date formats.
|
|
@@ -167,6 +167,44 @@ in `public/app.css`, and follow the app's [style variant](14-globalization.md#st
|
|
|
167
167
|
In SQL: `update meta.region set template_options = '{to-accent,to-compact}' where id = 12;` The
|
|
168
168
|
database only checks the shape (at most 12 names of lower case letters, digits and `-`).
|
|
169
169
|
|
|
170
|
+
### Notifications and live badges
|
|
171
|
+
|
|
172
|
+
Sprint 165 (APEX: a notification bell in the header, navigation menu badges, badges refreshed by
|
|
173
|
+
dynamic actions). Two settings under **Settings → User Interface → Notifications**:
|
|
174
|
+
|
|
175
|
+
- **Notification bell in the header.** Signed-in users get a bell next to their user menu: the number
|
|
176
|
+
of their unread notifications and the newest eight. Opening one marks it read and goes to its page;
|
|
177
|
+
*Mark all read* marks them all; *All notifications* is the page `/a/<alias>/notifications` (the
|
|
178
|
+
newest 100). Application code adds notifications with
|
|
179
|
+
[`meta.notify(user, title, body, page, items, icon, push)`](09-reference.md#in-app-notifications-from-sql)
|
|
180
|
+
or a [`send_notification`](06-processing.md#send-notification) process:
|
|
181
|
+
|
|
182
|
+
```sql
|
|
183
|
+
-- in a process after an order is submitted for approval
|
|
184
|
+
select meta.notify(:P5_APPROVER, 'Order ' || :P5_ID || ' needs your approval',
|
|
185
|
+
:P5_CUSTOMER || ', ' || :P5_TOTAL, 5, jsonb_build_object('P5_ID', :P5_ID), 'shopping-cart');
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The link is signed for the recipient, like a push notification's; `push => true` also sends it to
|
|
189
|
+
the user's devices when the application has [push notifications](17-mobile.md#push-notifications) on.
|
|
190
|
+
Titles and texts are escaped. A user keeps the newest 500 notifications; read ones go after 90 days.
|
|
191
|
+
Notifications are installation data: they are not part of the application's export.
|
|
192
|
+
|
|
193
|
+
- **Live badges (seconds).** Every so many seconds (10 to 3600) an open page asks the server for its
|
|
194
|
+
badges again and updates them in place, without a reload: the
|
|
195
|
+
[list entries](#list-lists) of the navigation menu and bar (a badge, a badge query or a query
|
|
196
|
+
list's `badge` column), [buttons with a badge query](06-processing.md#menu-buttons-and-badges) and
|
|
197
|
+
the bell. Not while the tab is hidden (at once when it shows again, and at once when a push
|
|
198
|
+
notification arrives); these requests don't count as activity, so an idle session still ends; a
|
|
199
|
+
signed-out session or a page the user may no longer open stops them. Empty badges are hidden
|
|
200
|
+
placeholders the refresh can fill.
|
|
201
|
+
|
|
202
|
+
A navigation bar entry with the unread count is a static list entry with the badge query
|
|
203
|
+
`select nullif(meta.unread_notifications(), 0)`. To refresh a region (a report of open orders, say)
|
|
204
|
+
on an interval, use a dynamic action with the [`timer`](07-dynamic-actions.md) event and
|
|
205
|
+
`refresh_region`. For a list of your own, `meta.my_notifications` is the current user's notifications
|
|
206
|
+
(`id, title, body, url, icon, created_at, read_at, unread`).
|
|
207
|
+
|
|
170
208
|
## Region types
|
|
171
209
|
|
|
172
210
|
| Type | Purpose |
|
|
@@ -1234,6 +1272,10 @@ or **navigation bar** (**Settings → Theme**: *Navigation menu list*, *Navigati
|
|
|
1234
1272
|
- **static**: its **entries** (Shared Components → Lists → a list → *Add entry*), each with a label,
|
|
1235
1273
|
an icon, a target, a badge, a description, a parent entry (a sub menu, up to 6 levels), a
|
|
1236
1274
|
sequence, a condition (SQL), an authorization scheme and a build option; or
|
|
1275
|
+
(sprint 165) a **badge query** instead of a badge: a SELECT whose first value is the badge, run as
|
|
1276
|
+
the application's role with the page's binds, e.g. `select nullif(count(*), 0) from sales.orders
|
|
1277
|
+
where status = 'NEW'` (no row or an empty value: no badge; a failing query is a page error and no
|
|
1278
|
+
badge); or
|
|
1237
1279
|
- **sql**: the rows of a query, run as the application's database role with the usual binds:
|
|
1238
1280
|
|
|
1239
1281
|
```sql
|
|
@@ -55,7 +55,9 @@ follows that button's condition and authorization instead.
|
|
|
55
55
|
A **badge** is a short value shown on the button (a count of open items, "new", …). `badge` is
|
|
56
56
|
text with `&ITEM.` substitutions (an empty result shows no badge); `badge_query` is a SELECT whose
|
|
57
57
|
first column of the first row is shown, and wins when both are set. The query runs as the
|
|
58
|
-
application's database role with bind variables, like any region source.
|
|
58
|
+
application's database role with bind variables, like any region source. (sprint 165) With
|
|
59
|
+
[live badges](04-pages-and-regions.md#notifications-and-live-badges) on, a badge query's value is
|
|
60
|
+
refreshed while the page is open.
|
|
59
61
|
|
|
60
62
|
## What happens on submit
|
|
61
63
|
|
|
@@ -141,6 +143,7 @@ two combine well; see error handling below.
|
|
|
141
143
|
| `chain` | An **execution chain**: run the processes that name it as their chain, in sequence, optionally in the background ([below](#execution-chains)) |
|
|
142
144
|
| `workflow` | Start a [workflow](#workflows), or terminate or retry an instance ([below](#workflow-processes)) |
|
|
143
145
|
| `send_push` | A [push notification](17-mobile.md#push-notifications) to a user's devices ([below](#send-push-notification)) |
|
|
146
|
+
| `send_notification` | (sprint 165) An in-app notification: the user's [notification bell](04-pages-and-regions.md#notifications-and-live-badges) ([below](#send-notification)) |
|
|
144
147
|
| `plugin` | A [process plug-in](04-pages-and-regions.md#plug-ins-with-their-own-code): calls its PL/pgSQL function with the attribute values (`config`: `{"plugin": "log_event", "attributes": {"DETAIL": "&P40_NOTE."}}`, `&ITEM.` filled in) as the application's role; the text it returns is the message |
|
|
145
148
|
|
|
146
149
|
| Property | Meaning |
|
|
@@ -418,6 +421,24 @@ queued in the page's transaction and sent after the submit commits; a user witho
|
|
|
418
421
|
nothing (no error). The application needs push notifications on (Settings → Progressive Web App).
|
|
419
422
|
From SQL the same is `meta.send_push(...)` ([chapter 9](09-reference.md#push-notifications-from-sql)).
|
|
420
423
|
|
|
424
|
+
### Send notification
|
|
425
|
+
|
|
426
|
+
Sprint 165. A `send_notification` process adds an in-app notification for one user: their
|
|
427
|
+
[notification bell](04-pages-and-regions.md#notifications-and-live-badges) shows it and its page opens
|
|
428
|
+
from there.
|
|
429
|
+
|
|
430
|
+
```json
|
|
431
|
+
{"to": "&P5_APPROVER.", "title": "Leave request from &APP_USER.", "body": "&P5_DAYS. days from &P5_START.",
|
|
432
|
+
"page": 5, "items": {"P5_ID": "&P5_ID."}, "icon": "calendar", "push": true}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
`to`, `title` and `body` take `&ITEM.` substitutions; `page` and `items` are the page it opens
|
|
436
|
+
(signed for the recipient); `icon` is an icon name (default `bell`); `push: true` also sends it to the
|
|
437
|
+
user's devices when push notifications are on. It is added in the page's transaction (a later failing
|
|
438
|
+
process leaves nothing) and can run in a background chain. The application needs notifications on
|
|
439
|
+
(Settings → User Interface → Notifications). From SQL the same is `meta.notify(...)`
|
|
440
|
+
([chapter 9](09-reference.md#in-app-notifications-from-sql)).
|
|
441
|
+
|
|
421
442
|
### Declarative process types
|
|
422
443
|
|
|
423
444
|
Sprint 157 (APEX: page process types). Each type calls what pgkiln already has; its settings go in
|
|
@@ -53,6 +53,7 @@ your own from a static application file.
|
|
|
53
53
|
| `calendar_view_change` | A calendar showed another view or period (APEX: View Change); the condition sees `month`, `week`, `day` or `list` | The calendar's id or static id |
|
|
54
54
|
| `map_object_click` | A marker or shape of a map was clicked (APEX: Map Object Clicked); the condition sees the row's `id` column, else its title | The map's id or static id |
|
|
55
55
|
| `map_change` | A map was moved or zoomed by the user (APEX: Map Changed); the condition sees the zoom level | The map's id or static id |
|
|
56
|
+
| `timer` | (sprint 165) Every so many seconds while the page is open and its tab is visible, e.g. `refresh_region` of a report of open orders | A number of seconds, 5 to 3600 |
|
|
56
57
|
| `tree_selection_change` | A tree node was clicked (APEX: Selection Change [Tree]); the condition sees its `id`, the node is marked; a `cancel_event` keeps its link closed | The tree's id or static id |
|
|
57
58
|
|
|
58
59
|
**The actions of one event and trigger run one after the other, in sequence order** (APEX: the true
|
|
@@ -133,6 +133,21 @@ queue; the owner can: `select id, username, status, devices, delivered, message
|
|
|
133
133
|
order by id desc` (`sent`: at least one device received it; `no_device`; `error`). Messages are kept
|
|
134
134
|
7 days.
|
|
135
135
|
|
|
136
|
+
### In-app notifications from SQL
|
|
137
|
+
|
|
138
|
+
Sprint 165: the [notification bell](04-pages-and-regions.md#notifications-and-live-badges); the
|
|
139
|
+
application needs notifications on (Settings → User Interface → Notifications).
|
|
140
|
+
|
|
141
|
+
| Function | Returns |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `meta.notify(p_user, p_title, p_body, p_page, p_items, p_icon, p_push)` | Adds a notification for `p_user` of the current application and returns its id. `p_title` (required, at most 200 characters), `p_body` (at most 1000), `p_page` + `p_items` (the page it opens with item values: the link, signed for the recipient), `p_icon` (an icon name), `p_push` (also to the user's devices when push notifications are on; default false) |
|
|
144
|
+
| `meta.unread_notifications(p_user)` | The number of unread notifications of the user (default: the current one), e.g. as a badge query |
|
|
145
|
+
| `meta.mark_notifications_read(p_id)` | Marks the current user's notification `p_id` read, or all of them (null); returns how many |
|
|
146
|
+
| `meta.my_notifications` | A view: the current user's notifications (`id, title, body, url, icon, created_at, read_at, unread`) |
|
|
147
|
+
|
|
148
|
+
A notification exists once the transaction commits. A user keeps the newest 500; read ones go after
|
|
149
|
+
90 days. Application roles reach the table only through these functions and the view.
|
|
150
|
+
|
|
136
151
|
### AI requests from SQL
|
|
137
152
|
|
|
138
153
|
`meta.ai_generate(service, prompt, system, schema)` queues a request to an
|
|
@@ -61,6 +61,7 @@ What's missing is listed in the [feature parity matrix](../apex-feature-parity.m
|
|
|
61
61
|
| Interactive report *Download → PDF*, printing | Actions → Download PDF, Print ([chapter 16](16-files.md#printing)) |
|
|
62
62
|
| Pagination *Row Ranges X to Y*, *Maximum Row Count*, region *Lazy Loading*, *Server Cache* | `"pagination": "range"`, `max_rows`, `"lazy": true`, `"cache": {"scope", "seconds"}` ([large tables](04-pages-and-regions.md#large-tables)) |
|
|
63
63
|
| `APEX_UTIL.CHANGE_CURRENT_USER_PW`, `RESET_PASSWORD`, `EXPIRE_END_USER_ACCOUNT` | My account page; `meta.set_password()`, `meta.expire_password()` ([chapter 8](08-security.md#passwords-and-my-account)) |
|
|
64
|
+
| Navigation menu badges, a notification bell in the header, badges refreshed with dynamic actions and JavaScript | A list entry's `badge` or `badge_query` (also in the navigation bar), Settings → User Interface → *Notification bell* with `meta.notify(user, title, body, page, items)` or the process type `send_notification`, *Live badges* (seconds) and the dynamic action event `timer` ([chapter 4](04-pages-and-regions.md#notifications-and-live-badges)) |
|
|
64
65
|
| Progressive Web App push notifications: `APEX_PWA.SEND_PUSH_NOTIFICATION`, `HAS_PUSH_SUBSCRIPTION`, *Send Push Notification* process, the subscription settings page | `meta.send_push(user, title, body, page, items)`, `meta.has_push_subscription(user)`, process type `send_push`, *My account → Notifications* and the dynamic action `push_subscribe` ([chapter 17](17-mobile.md#push-notifications)) |
|
|
65
66
|
| `APEX_MAIL`, Send E-Mail process, e-mail templates | `meta.send_mail`, `meta.send_mail_template`, `meta.add_mail_attachment`, the `send_email` process and Shared Components → E-mail templates (`#NAME#` placeholders) ([chapter 6](06-processing.md#sending-e-mail)). Instead of the instance's SMTP settings: mail services with SMTP (password or OAuth2), Microsoft Graph, Amazon SES, Resend, Postmark, SendGrid or Mailgun. `APEX_MAIL.PUSH_QUEUE` is not needed: mail goes out right after the commit |
|
|
66
67
|
| `APEX_APP_SETTING`, `APEX_UTIL.GET/SET/REMOVE_PREFERENCE`, *Maintain session state: Per User* | Shared Components → Application settings and `meta.get_app_setting`/`meta.set_app_setting`; `meta.get_preference`/`set_preference`/`remove_preference`; `{"persist": "user"}` on an item ([chapter 6](06-processing.md#application-settings)) |
|
|
@@ -328,6 +328,10 @@ src/
|
|
|
328
328
|
availability.ts availabilityRefusal(): who may use an application (migration 083); loadContext, the login page
|
|
329
329
|
and REST modules ask it (PostgREST: meta.api_check)
|
|
330
330
|
feedback.ts users' feedback page and the navigation bar entry (migration 084)
|
|
331
|
+
notifications.ts (sprint 165, migration 154) the notification bell (bellMarkup: unread count, newest 8, Mark all read),
|
|
332
|
+
/a/:alias/notifications, POST …/notifications/:id/open (the user's own only, marks read, its signed
|
|
333
|
+
link under the current alias) and …/notifications/read; GET …/:page/live (liveState: list entry
|
|
334
|
+
badges n:/b:<key>, buttons btn:<id>, the bell) with loadContext touch: false (no last_seen, no new session)
|
|
331
335
|
data-items.ts display image and address items (migration 086): imageUrl (https origins into the CSP's img-src),
|
|
332
336
|
GET …/image/:item (a BLOB from the item's query), POST …/geocode/:item, and items kept per user
|
|
333
337
|
(loadPreferences / savePreferences through meta.get_preference / set_preference)
|
|
@@ -611,6 +615,8 @@ test/
|
|
|
611
615
|
fixtures/ test files (employees.xlsx)
|
|
612
616
|
template-components.test.ts template language, escaping, plug-ins, regions and column templates
|
|
613
617
|
logic.test.ts computations, branches, menu buttons and badges, new dynamic actions, build options, export
|
|
618
|
+
notifications.test.ts (165) meta.notify and its checks, the bell, opening and Mark all read, list entry badge queries,
|
|
619
|
+
the live endpoint, send_notification, the timer event, the settings (security cases in security.test.ts)
|
|
614
620
|
page-logic.test.ts download, chain (background jobs) and workflow processes, function/app branches, dialog_closed (HR page 28)
|
|
615
621
|
code-editor.test.ts code editor: completions scoped to the app's role, the check, marked fields
|
|
616
622
|
builder-i18n.test.ts (sprint 121) builder languages: the coverage check of src/builder against i18n/en.ts, the message
|
|
@@ -684,6 +690,7 @@ test/
|
|
|
684
690
|
e2e/globalization.test.ts the browser's time zone (sign-in, app.js), no-JavaScript fallback, a masked number item
|
|
685
691
|
e2e/grid.test.ts interactive grid in a browser: master-detail refresh, move/resize columns, row menu, copy/paste; without JavaScript
|
|
686
692
|
e2e/page-logic.test.ts dialog_closed refreshes a region without a reload, download process in a browser, dialog link without JavaScript
|
|
693
|
+
e2e/notifications.test.ts (165) a notification reaches the open page (bell and navigation bar badge), opened from the bell; a timer action
|
|
687
694
|
e2e/ai-builder.test.ts App Builder AI in a browser (mock Claude): SQL from a question, a drafted description, proposed pages,
|
|
688
695
|
a blueprint drafted and reviewed
|
|
689
696
|
e2e/ai-assistant.test.ts the AI assistant and report questions on HR page 38 in a browser (mock Claude), with and without JavaScript
|
package/package.json
CHANGED
package/public/app.css
CHANGED
|
@@ -280,6 +280,35 @@ h2 { font-size: 1rem; margin: 0; font-weight: 600; }
|
|
|
280
280
|
.t-user > summary::-webkit-details-marker { display: none; }
|
|
281
281
|
.t-user > summary:hover { background: rgb(255 255 255 / 10%); }
|
|
282
282
|
|
|
283
|
+
/* (165) the notification bell: the unread count on the icon, the newest notifications in its menu */
|
|
284
|
+
.t-bell > summary { position: relative; }
|
|
285
|
+
.t-bell-count {
|
|
286
|
+
position: absolute; top: 1px; inset-inline-end: 0; min-width: 1.15rem; height: 1.15rem; padding: 0 0.3rem; border-radius: 99px;
|
|
287
|
+
background: #d92d20; color: #fff; font-size: 0.68rem; font-weight: 700; line-height: 1.15rem; text-align: center;
|
|
288
|
+
box-shadow: 0 0 0 2px var(--header, #1d1b31);
|
|
289
|
+
}
|
|
290
|
+
.t-bell-panel { width: min(380px, calc(100vw - 1rem)); }
|
|
291
|
+
.t-bell-head { flex-direction: row; align-items: center; justify-content: space-between; }
|
|
292
|
+
.t-bell-read { margin: 0; }
|
|
293
|
+
.t-bell-read .link-button { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.85rem; }
|
|
294
|
+
.t-bell-none { padding: 0.5rem 0.9rem; margin: 0; }
|
|
295
|
+
.t-bell-list, .t-notification-list { list-style: none; margin: 0; padding: 0; }
|
|
296
|
+
.t-bell-list form, .t-notification-list form { margin: 0; }
|
|
297
|
+
.t-notification {
|
|
298
|
+
display: flex; gap: 0.6rem; align-items: flex-start; width: 100%; padding: 0.55rem 0.9rem; border: 0; background: none;
|
|
299
|
+
color: var(--text); font: inherit; text-align: start; cursor: pointer;
|
|
300
|
+
}
|
|
301
|
+
.t-notification:hover, .t-notification:focus-visible { background: var(--surface-2, rgb(0 0 0 / 4%)); }
|
|
302
|
+
.t-notification > .icon { flex: none; margin-top: 0.15rem; color: var(--muted); }
|
|
303
|
+
.t-notification-text { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
|
|
304
|
+
.t-notification-title { font-weight: 500; }
|
|
305
|
+
.t-notification-body { color: var(--muted); font-size: 0.88rem; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
|
306
|
+
.t-notification-time { color: var(--muted); font-size: 0.78rem; }
|
|
307
|
+
.is-unread > form > .t-notification { box-shadow: inset 3px 0 0 var(--accent); }
|
|
308
|
+
.is-unread .t-notification-title { font-weight: 700; }
|
|
309
|
+
.is-unread > form > .t-notification > .icon { color: var(--accent); }
|
|
310
|
+
.t-notification-list > li + li { border-top: 1px solid var(--border); }
|
|
311
|
+
|
|
283
312
|
/* ---------- menus (details dropdowns) ---------- */
|
|
284
313
|
details.menu { position: relative; }
|
|
285
314
|
details.menu > summary { list-style: none; }
|
package/public/app.js
CHANGED
|
@@ -915,6 +915,16 @@ const pgkilnUnsaved = (() => {
|
|
|
915
915
|
requestAnimationFrame(() => { queued = false; runChain(run); });
|
|
916
916
|
}, { passive: true });
|
|
917
917
|
}
|
|
918
|
+
// (165) timer: every so many seconds (the trigger), while the page is visible
|
|
919
|
+
{
|
|
920
|
+
const bySeconds = new Map();
|
|
921
|
+
for (const da of das.filter((d) => d.event === 'timer')) {
|
|
922
|
+
const seconds = Number(da.trigger[0]);
|
|
923
|
+
if (!(seconds >= 5 && seconds <= 3600)) continue;
|
|
924
|
+
bySeconds.set(seconds, [...(bySeconds.get(seconds) || []), da]);
|
|
925
|
+
}
|
|
926
|
+
for (const [seconds, run] of bySeconds) setInterval(() => { if (!document.hidden) runChain(run); }, seconds * 1000);
|
|
927
|
+
}
|
|
918
928
|
// page_unload: the page is left (only quick, local actions finish)
|
|
919
929
|
window.addEventListener('pagehide', () => runChain(das.filter((da) => da.event === 'page_unload')));
|
|
920
930
|
// facets_change: a faceted search region's facets were applied (the page came back from its form)
|
|
@@ -2851,6 +2861,66 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2851
2861
|
for (const form of document.querySelectorAll('form[data-autosubmit]')) form.submit();
|
|
2852
2862
|
});
|
|
2853
2863
|
|
|
2864
|
+
// (165) Live badges and the notification bell (body data-live: seconds; GET <base>/<page>/live).
|
|
2865
|
+
const liveBadges = (() => {
|
|
2866
|
+
let seconds = 0;
|
|
2867
|
+
let timer = 0;
|
|
2868
|
+
let last = 0;
|
|
2869
|
+
let busy = false;
|
|
2870
|
+
let stopped = false;
|
|
2871
|
+
const apply = (state) => {
|
|
2872
|
+
for (const el of document.querySelectorAll('[data-badge]')) {
|
|
2873
|
+
const key = el.dataset.badge;
|
|
2874
|
+
if (key === 'bell' || !(key in state.badges)) continue;
|
|
2875
|
+
const v = state.badges[key];
|
|
2876
|
+
el.textContent = v ?? '';
|
|
2877
|
+
el.hidden = v === null || v === '';
|
|
2878
|
+
}
|
|
2879
|
+
const bell = state.bell && document.querySelector('[data-bell]');
|
|
2880
|
+
if (!bell) return;
|
|
2881
|
+
const count = bell.querySelector('[data-badge="bell"]');
|
|
2882
|
+
if (count) {
|
|
2883
|
+
count.textContent = state.bell.unread ? (state.bell.unread > 99 ? '99+' : String(state.bell.unread)) : '';
|
|
2884
|
+
count.hidden = !state.bell.unread;
|
|
2885
|
+
}
|
|
2886
|
+
const summary = bell.querySelector('summary');
|
|
2887
|
+
if (summary) {
|
|
2888
|
+
summary.title = state.bell.label;
|
|
2889
|
+
summary.setAttribute('aria-label', state.bell.label);
|
|
2890
|
+
}
|
|
2891
|
+
// the list is replaced while the menu is closed only (not under the user's pointer or focus)
|
|
2892
|
+
const panel = bell.querySelector('[data-bell-panel]');
|
|
2893
|
+
if (panel && !bell.open && panel.innerHTML !== state.bell.panel) panel.innerHTML = state.bell.panel;
|
|
2894
|
+
};
|
|
2895
|
+
const poll = () => {
|
|
2896
|
+
const base = document.body.dataset.base;
|
|
2897
|
+
const page = document.body.dataset.page;
|
|
2898
|
+
if (stopped || busy || !base || !page) return;
|
|
2899
|
+
busy = true;
|
|
2900
|
+
last = Date.now();
|
|
2901
|
+
fetch(`${base}/${page}/live`, { credentials: 'same-origin', headers: { Accept: 'application/json' } })
|
|
2902
|
+
.then((r) => {
|
|
2903
|
+
if (r.status === 401 || r.status === 403 || r.status === 404) stopped = true;
|
|
2904
|
+
return r.ok ? r.json() : null;
|
|
2905
|
+
})
|
|
2906
|
+
.then((state) => state && state.badges && apply(state))
|
|
2907
|
+
.catch(() => {})
|
|
2908
|
+
.finally(() => { busy = false; });
|
|
2909
|
+
};
|
|
2910
|
+
return {
|
|
2911
|
+
start(s) {
|
|
2912
|
+
if (!(s >= 10) || timer) return;
|
|
2913
|
+
seconds = s;
|
|
2914
|
+
timer = setInterval(() => { if (!document.hidden) poll(); }, seconds * 1000);
|
|
2915
|
+
// back to a tab that was hidden longer than the interval: at once
|
|
2916
|
+
document.addEventListener('visibilitychange', () => { if (!document.hidden && Date.now() - last > seconds * 1000) poll(); });
|
|
2917
|
+
},
|
|
2918
|
+
now() {
|
|
2919
|
+
if (seconds) poll();
|
|
2920
|
+
},
|
|
2921
|
+
};
|
|
2922
|
+
})();
|
|
2923
|
+
|
|
2854
2924
|
// ------------------------------------------------------------------ Progressive Web App and field work
|
|
2855
2925
|
(() => {
|
|
2856
2926
|
const metaEl = document.getElementById('pgkiln-meta');
|
|
@@ -2879,10 +2949,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2879
2949
|
}
|
|
2880
2950
|
navigator.serviceWorker.addEventListener('message', (e) => {
|
|
2881
2951
|
if (e.data && e.data.type === 'pgkiln:queue') showQueue(e.data.items, post);
|
|
2952
|
+
// (165) a push notification arrived: the badges and the bell now, not at the next refresh
|
|
2953
|
+
if (e.data && e.data.type === 'pgkiln:pushed') liveBadges.now();
|
|
2882
2954
|
});
|
|
2883
2955
|
if (new URLSearchParams(location.search).get('queued') === '1') note(t('pwa.queued'));
|
|
2884
2956
|
}
|
|
2885
2957
|
|
|
2958
|
+
// (165) live badges: every data-live seconds the page asks for its badges (navigation menu and bar,
|
|
2959
|
+
// buttons with a badge query) and the bell, and updates them in place; not while the tab is hidden.
|
|
2960
|
+
// A signed-out session (401) or a page without them (404, 403) stops it.
|
|
2961
|
+
liveBadges.start(Number(body.dataset.live) || 0);
|
|
2962
|
+
|
|
2886
2963
|
// offline: say so (the page may come from the device)
|
|
2887
2964
|
const banner = () => {
|
|
2888
2965
|
let b = document.querySelector('.offline-banner');
|
|
@@ -5241,3 +5318,4 @@ document.addEventListener('change', (e) => {
|
|
|
5241
5318
|
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') send(); });
|
|
5242
5319
|
window.addEventListener('pagehide', send);
|
|
5243
5320
|
})();
|
|
5321
|
+
|