diff --git a/targets/hr/HrTarget/Program.cs b/targets/hr/HrTarget/Program.cs
index 486d6d7..70957b2 100644
--- a/targets/hr/HrTarget/Program.cs
+++ b/targets/hr/HrTarget/Program.cs
@@ -110,7 +110,7 @@ app.MapGet("/api/employees/{id}/has-history", async (string id) =>
     Results.Ok(new { history = (await Read("select 1 as x from emp_history_view where employee_id = $1::numeric limit 1", id)).Count > 0 }));
 
 // COMMIT_FORM for the EMPLOYEES block: deletes, then inserts and updates in record order, one transaction.
-// PRE-INSERT draws employees_seq.nextval; Forms updates every base-table column of a changed record.
+// PRE-INSERT draws employees_seq.nextval (the column default here); Forms updates every base-table column of a changed record.
 string[] empCols = ["first_name", "last_name", "email", "phone_number", "hire_date", "job_id", "salary", "commission_pct", "manager_id", "department_id"];
 var numeric = new HashSet<string> { "salary", "commission_pct", "manager_id", "department_id", "employee_id" };
 app.MapPost("/api/employees/commit", async (CommitRequest req) =>
@@ -136,14 +136,10 @@ app.MapPost("/api/employees/commit", async (CommitRequest req) =>
             if (r.Key == null)
             {
                 op = "INSERT";
-                await using var seq = new NpgsqlCommand("select nextval('employees_seq')::text", conn, tx);
-                var newId = (string)(await seq.ExecuteScalarAsync())!;
-                ids.Add(newId);
                 await using var cmd = new NpgsqlCommand(
-                    $"insert into employees (employee_id, {string.Join(", ", empCols)}) values ($1::numeric, {string.Join(", ", empCols.Select((c, i) => "$" + (i + 2) + Cast(c)))})", conn, tx);
-                cmd.Parameters.AddWithValue(newId);
+                    $"insert into employees ({string.Join(", ", empCols)}) values ({string.Join(", ", empCols.Select((c, i) => "$" + (i + 1) + Cast(c)))}) returning employee_id::text", conn, tx);
                 foreach (var c in empCols) cmd.Parameters.AddWithValue((object?)r.Values.GetValueOrDefault(c) ?? DBNull.Value);
-                await cmd.ExecuteNonQueryAsync();
+                ids.Add((string)(await cmd.ExecuteScalarAsync())!);
             }
             else
             {
diff --git a/targets/hr/HrTarget/wwwroot/app.js b/targets/hr/HrTarget/wwwroot/app.js
index 43e6250..8d8cf38 100644
--- a/targets/hr/HrTarget/wwwroot/app.js
+++ b/targets/hr/HrTarget/wwwroot/app.js
@@ -185,16 +185,18 @@ async function api(path, body) {
 
 // One user action at a time, in order, like the Forms runtime's single thread. Messages issued during an
 // action are pending: a second message pushes the first into a modal alert (Forms shows it there).
-function enqueue(fn) {
+// The record indicator is refreshed when a message is written and when an action ends; a lazy action (the
+// mouse navigation to a button, whose trigger runs next) leaves it as it was.
+function enqueue(fn, lazy) {
   S.queue = S.queue.then(async () => {
     try { await fn(); }
     catch (e) { console.error(e); setMessage(String(e.message || e)); }
-    finally { S.actionMsg = null; updatePosition(); }
+    finally { S.actionMsg = null; if (!lazy) updatePosition(); }
   });
   return S.queue;
 }
 
-function setMessage(text) { $('message').textContent = text; }
+function setMessage(text) { $('message').textContent = text; updatePosition(); }
 
 async function msg(text) {
   if (S.actionMsg !== null) await showAlert({ title: 'Forms', message: S.actionMsg, buttons: ['OK'], icon: 'note' });
@@ -298,6 +300,7 @@ async function exitForm(f, mode) {
   }
   S.forms.splice(S.forms.indexOf(f), 1);
   f.el.remove();
+  setMessage('');
   if (S.forms.length) { activateForm(S.forms[S.forms.length - 1]); focusCursor(); return; }
   S.ended = true;
   S.active = null;
@@ -306,8 +309,14 @@ async function exitForm(f, mode) {
   document.title = 'HR APP';
 }
 
-const askCommit = () => showAlert({ title: 'Forms', message: 'Do you want to save the changes you have made?',
-                                    buttons: ['Yes', 'No', 'Cancel'], icon: 'caution' });
+async function askCommit() {
+  const before = $('message').textContent;
+  $('message').textContent = 'Press Tab to move between choices, Return to select.';
+  const a = await showAlert({ title: 'Forms', message: 'Do you want to save the changes you have made?',
+                              buttons: ['Yes', 'No', 'Cancel'], icon: 'caution' });
+  $('message').textContent = before;
+  return a;
+}
 
 function activateForm(f) {
   S.active = f;
@@ -452,11 +461,12 @@ async function fill(f, block, path) {
   const b = f.blocks[block];
   b.records = rows.map(r => fromRow(b.def, r));
   b.cur = 0; b.top = 0;
+  queried(b, rows.length);
 }
 
 function clearBlock(f, block) {
   const b = f.blocks[block];
-  b.records = []; b.deleted = []; b.cur = 0; b.top = 0;
+  b.records = []; b.deleted = []; b.cur = 0; b.top = 0; delete b.queried;
   if (f.cursor && f.cursor.block === block) { b.records.push(newRecord()); f.cursor = { ...f.cursor, rec: 0 }; }
   renderBlock(f, block);
 }
@@ -474,6 +484,7 @@ async function executeQuery(f) {
   if (res.error) { await msg(res.error); return false; }
   b.records = res.map(r => fromRow(b.def, r));
   b.deleted = []; b.cur = 0; b.top = 0;
+  queried(b, res.length);
   if (!b.records.length) { b.records = [newRecord()]; await msg('FRM-40350: Query caused no records to be retrieved.'); }
   const item = f.cursor && f.cursor.block === 'EMPLOYEES' ? f.cursor.item : navItems(f, 'EMPLOYEES')[0].name;
   f.cursor = { block: 'EMPLOYEES', item, rec: 0 };
@@ -673,7 +684,7 @@ function showAlert(a) {
     const buttons = a.buttons.map(label => {
       const b = document.createElement('button');
       b.type = 'button'; b.textContent = label;
-      b.addEventListener('click', () => { dlg.hidden = true; S.modal = null; box.innerHTML = ''; resolve(label); });
+      b.addEventListener('click', () => { dlg.hidden = true; S.modal = null; box.innerHTML = ''; focusCursor(); resolve(label); });
       box.append(b);
       return b;
     });
@@ -722,7 +733,7 @@ function renderForm(f) {
     for (const p of t.pages) {
       const tb = el('button'); tb.type = 'button'; tb.textContent = p.label; tb.tabIndex = -1;
       tb.id = 'tab-' + p.name.toLowerCase(); tb.dataset.page = p.name;
-      tb.addEventListener('mousedown', e => e.preventDefault());
+      tb.setAttribute('role', 'tab');     // a click focuses the tab bar; the cursor stays where it was
       tb.addEventListener('click', () => enqueue(async () => { if (!S.modal) selectPage(f, p.name); }));
       bar2.append(tb);
       const page = el('div', 'tabpage', { top: '22px' }); page.dataset.page = p.name; page.id = 'page-' + p.name.toLowerCase();
@@ -809,7 +820,7 @@ function renderBlock(f, block) {
       }
     }
   }
-  updatePosition();
+  fetchShown(b);
 }
 
 function scrollTo(f, block) {
@@ -845,14 +856,24 @@ function focusCursor() {
   try { if (document.activeElement !== e) e.focus(); } finally { S.prog--; }
   for (const r of f.containers[f.cursor.block].querySelectorAll('.rec'))
     r.classList.toggle('current', !f.blocks[f.cursor.block].def.control && +r.dataset.row + f.blocks[f.cursor.block].top === f.blocks[f.cursor.block].cur);
-  updatePosition();
+}
+
+// A queried block fetches its rows in batches of the display count as they come into view; the record count
+// is known ('Record: n/m') only once a fetch came back short, until then it reads 'n/?'.
+function queried(b, n) { b.queried = n; b.batches = 1; }
+
+function fetchShown(b) {
+  if (b.queried === undefined) return;
+  const size = b.def.display || 1;
+  b.batches = Math.max(b.batches, Math.ceil((b.top + size) / size));
 }
 
 function updatePosition() {
   const f = S.active;
   if (!f || !f.cursor) { $('position').textContent = ''; return; }
   const b = f.blocks[f.cursor.block];
-  $('position').textContent = `Record: ${b.records.length ? b.cur + 1 : 0}/${b.records.length}`;
+  const known = b.queried === undefined || b.batches * (b.def.display || 1) > b.queried;
+  $('position').textContent = `Record: ${b.records.length ? b.cur + 1 : 0}/${known ? b.records.length : '?'}`;
 }
 
 // ---- events -------------------------------------------------------------------------------------------
@@ -881,7 +902,7 @@ document.addEventListener('focusin', e => {
     if (!S.forms.includes(r.f)) return;
     if (S.active !== r.f) activateForm(r.f);
     await navigate(r.f, r.ref);
-  });
+  }, e.target.tagName === 'BUTTON');
 });
 
 document.addEventListener('click', e => {
diff --git a/targets/hr/README.md b/targets/hr/README.md
index 57d7bcf..d0c2ca8 100644
--- a/targets/hr/README.md
+++ b/targets/hr/README.md
@@ -26,7 +26,9 @@ given as `HR_DATABASE`. Users: `alice` / `alice#123` (ADMIN_ROLE), `bob` / `bob#
 
 ## Deploy on Linux behind a reverse proxy
 
-On Ubuntu 24.04 with the .NET 10 runtime and PostgreSQL 16:
+On Ubuntu 24.04 with the .NET 10 runtime (`aspnetcore-runtime-10.0`), `libgssapi-krb5-2` (Npgsql logs a warning
+without it) and PostgreSQL 16. Checked in the `mcr.microsoft.com/dotnet/aspnet:10.0-noble` image under `PATH_BASE=/hr`
+([evidence](../../docs/artifacts/hr-target/linux/linux-check.txt)):
 
 ```
 dotnet publish targets/hr/HrTarget -c Release -o /opt/hr
diff --git a/targets/hr/schema.sql b/targets/hr/schema.sql
index df34148..0f73fbb 100644
--- a/targets/hr/schema.sql
+++ b/targets/hr/schema.sql
@@ -41,8 +41,9 @@ create table jobs (
   max_salary numeric(6)
 );
 
+create sequence employees_seq start with 207 increment by 1 no cycle;
 create table employees (
-  employee_id    numeric(6) constraint emp_emp_id_pk primary key,
+  employee_id    numeric(6) default nextval('employees_seq') constraint emp_emp_id_pk primary key,  -- PRE-INSERT
   first_name     varchar(20),
   last_name      varchar(25) constraint emp_last_name_nn not null,
   email          varchar(25) constraint emp_email_nn not null constraint emp_email_uk unique,
@@ -55,7 +56,6 @@ create table employees (
   department_id  numeric(4) constraint emp_dept_fk references departments (department_id)
 );
 alter table departments add constraint dept_mgr_fk foreign key (manager_id) references employees (employee_id);
-create sequence employees_seq start with 207 increment by 1 no cycle;
 
 create table job_history (
   employee_id   numeric(6) constraint jhist_employee_nn not null constraint jhist_emp_fk references employees (employee_id),
