Repository navigation
v16.0 browser test checklist — exercise every 16.0 feature in the running app #3358
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentation
on Jul 20, 2026 v16.0 verification sweep — results (automated dogfood pass)
Ran the checklist against the showcase app on a fresh isolated
os devinstance (frameworkmain@ d8b8354, console16.0.0-rc.1, vendored objectui pin69fa5d16— confirmed to include all post-rc.0 Console workaf1b0db+). Drove it as admin in the browser + REST/CLI, with build-time gates exercised against the built packages.🐛 Confirmed regressions filed
Both are the same architectural seam: a v16 feature wired into the runtime
HttpDispatcher/ dispatcher-plugin that never reaches the hono serveros serve/devactually runs.- v16.0: per-request admin-gated Server-Timing never emits on the hono server (os serve/dev) #3361 — per-request admin-gated
Server-Timingnever emits on the hono server (§9).X-OS-Debug-Timing: 1|jsonas admin → no header on any route.allowPerfDisclosure()is only called by the runtime dispatcher; hono resolves identity via its ownresolveCtx. Unit test simulates the call, hiding the gap. - v16.0: mark-notification-read 404s on os dev/serve — unread never clears (#3354 not effective on hono server) #3362 — mark-notification-read 404s on os dev → unread never clears (§7, the
⚠️ fix(i18n/notifications): localize collab notification titles + storage objects; wire the notifications REST routes #3354 item).POST /api/v1/notifications/read+/read/all→ 404; data-API receipt write → 405 (ADR-0103 engine-owned). The console (AppHeader.tsx:483) uses the 404ing routes, so no mark-read path works. The fix(i18n/notifications): localize collab notification titles + storage objects; wire the notifications REST routes #3354 route registrations live indispatcher-plugin.ts:704and don't bind to the hono listener. Suggest a framework-wide audit of which/api/v1/*routes are hono-native vs dispatcher-only (MCP HTTP/mcpalso returns 501 from the same block).
✅ Verified PASS
§9 Backend/API/CLI (8/9):
- transactionalBatch discovery bit =
true+/api/v1/batchmounted;atomic:false→ 400BATCH_NOT_ATOMIC readonlystripped on INSERT (lead_score=999 → stored null)- Formula fixes (harness vs built
@objectstack/formula): date-arithend-start+1&today()+30→ build error;due_date==today(),cond?5:null,floor,ceil,daysBetween→ ok - enforce-or-remove:
ObjectSchema.createthrows located guidance forrecordName/search/versioning/softDelete/keyPrefix/tags/abstract; fieldreferenceFilterssilently stripped - checkboxes per-option
visibleWhenserver-side:CHOICE_FIELD_TYPESincludescheckboxes; live cascade write (country=cn, province=ca) → 400VALIDATION_FAILED organizationIdon author ctx (notenantIdalias); MCP stdio fail-closed guard present (plugin.ts:178)
§2 Flow (Studio designer): time-relative trigger panel (Sweep object / Date field / Offset days numberList); nested regions expand inline (Loop
{tasks}, not opaque card, incl. #3304 clean{tasks}); schema-drivennumberList; observability banner (Flows: 43 flow(s) 34 bound … time_relative … · 6 draft).§3 Dashboards:
⚠️ strict widgets — decisive (categoryField/valueField+aggregate/pivotrowField/hallucinated key all rejected with the loud named ADR-0021 error);⚠️ chart first paint (bars/axes draw immediately, no blank-until-resize).§1 Approvals (authoring):
showcase_committee_quorum→ approval node "Committee Sign-off (2 of 3)", schema-driven inspector with per-approver Type/Value/Group (会签). quorum/per_group/minApprovals present in the server-driven palette (/automation/actions).§5 Access pillar: matrix on first screen w/ zero-grant capabilities minimal (B1); provenance badges (包内置, OWD Public/Private/parent-controlled, Ext); read-only package lock (只读 badge); Explain-access button present.
§6 / §8: dev-admin login hint; Gantt renders (granularity, bars, milestones, dependencies, auto-schedule wand); mobile QR-share action removed (objectui#2687); auto-schedule confirm-first dialog present.
⚠️ Blocked / not fully driven (need setup or deeper interaction)- §1 live approval flow — showcase seeds 0 positions / 0 invoices / 0 pending requests, so the end-to-end inbox scenario (progress badges, decision attachments,
?request=deep link, viewer gating, reassign picker) isn't demonstrable out of the box. The checklist's "ExpenseSignoffFlow + finance/legal approvers" assumption doesn't hold without seeding. - §2 Flow Runs step logs — 0 automation runs (no flow has fired a loop yet).
- §9 MCP
validate_expression— MCP HTTP is off (501) on this config; the underlyingvalidateExpressionfn works. - §9 owner_id forge — needs a non-admin session to be meaningful (admin write is legitimate).
- §4 remaining (action-param widgets, upload guard+autonumber, master-detail atomic save, related-list pagination, History tab, inline edit, import wizard, single-file grid) — not yet driven;
visibleWhencascade dependsOn gating + list refresh button confirmed. - §6 SSO-only cold-start, import auto-policy default, phone sign-in surfaces — not yet driven.
Automated pass; happy to drive the blocked items with proper seeding in a follow-up.
- v16.0: per-request admin-gated Server-Timing never emits on the hono server (os serve/dev) #3361 — per-request admin-gated
- added a commit that references this issue
on Jul 21, 2026 §4 Import wizard — ticked on test evidence, not a browser click-through
This item stayed
⚠️ because the wizard starts at a native file picker, which the browser sweep can't drive. I went to write a Playwright e2e for it and found that would be redundant: all three clauses of this checklist line are already covered by targeted tests inobjectui, and they assert the exact behaviours worded here.Checklist clause Test Asserts GBK/GB18030 zh-CN CSV without garbled headers packages/plugin-grid/src/importParsers.test.ts:115Feeds real GBK bytes and asserts the exact decode [['名称 *','编号 *'],['测试岛2','TEST-001']]— mojibake would fail it. Siblings cover UTF-8-no-BOM, UTF-8 BOM strip, UTF-16LE BOM.A disabled Next explains itself (unmapped required hint) packages/plugin-grid/src/__tests__/importMissingRequiredHint.test.tsx:40,50Hint reads Lifecycle (status)(thelabel (name)form) andimport-next-btnisdisabled; supplying the column clears the hint live, without a remount.Legacy per-row fallback shows a "compatibility fallback" notice packages/plugin-grid/src/__tests__/importLegacyReferenceGuard.test.tsx:82Result carries degraded === true,import-degraded-noticerenders, and the copy matches/compatibility fallback/i— i.e. the downgrade is not silent (#2639).Run just now on
objectui@main:pnpm --filter @object-ui/plugin-grid exec vitest run \ src/importParsers.test.ts \ src/__tests__/importMissingRequiredHint.test.tsx \ src/__tests__/importLegacyReferenceGuard.test.tsx → Test Files 3 passed (3) · Tests 35 passed (35)The decode path itself is real, not test-only:
importParsers.ts:118-129sniffs BOM → strict UTF-8 (whose validation rejects GBK multi-byte runs) → GB18030 fallback, with a documented degrade for small-ICU runtimes lacking thegb18030decoder.What is still not exercised anywhere: the native
<input type=file>picker hand-off itself. That's the flakiest, lowest-value slice to automate, and it is not what this line asserts — so I'm ticking the box on the evidence above rather than adding a redundant e2e. Happy to reopen if you'd rather have the full-stack e2e regardless.§4 — browser-verified the two items shipped in #3393
Both were made demonstrable by #3393 but I had only confirmed them programmatically. Drove them in the running showcase (
os dev, fresh seed) and they hold up, so I'm ticking them.Action param real widgets — ran
Action Param Galleryfrom the Field Zoo row menu and read the dialog's DOM. Every param renders its real widget, not a text input:Param Rendered as Titleinput[type=text]Rich noterich-text editor ( 格式: markdown), not a plaintextareaPrioritycustom select ( button), not a text inputEffective dateinput[type=date]Accent colorinput[type=color](swatch +#7C3AED)Reference #AutoNumber — read-only —, server-assignedCover imageinput[type=file]accept="image/*"Attachmentsinput[type=file]accept="application/pdf,image/*"+multipleSo
multipleandacceptland as real DOM attributes, with distinct values per param. Caveat:maxSizeis enforced in JS, not as an HTML attribute, so it isn't directly observable this way — I did not verify it.Related lists pagination — Northwind (26 contacts). The related list is genuinely server-paged, not client-sliced:
GET /api/v1/data/showcase_contact?top=1&filter=["account","=","aKCTDUYTmJgn4xTU"] ← count probe GET /api/v1/data/showcase_contact?top=5&filter=[...] ← page 1 GET /api/v1/data/showcase_contact?top=5&skip=5&filter=[...] ← after 下一页Page 1 rendered 5 of 26 rows; clicking 下一页 issued
top=5&skip=5and the grid switched toProspect 04…08with the pager reading第 2 页,共 6 页. It never fetches all 26.Still not ticked, deliberately
⚠️ Upload guard + autonumber (the line above) — the dialog does carry the copy "Confirm stays disabled while a file is still uploading (ADR-0059 upload guard)", and the AutoNumber half is confirmed above, but I did not prove the guard actually enforces: that needs a real upload caught mid-flight. Leaving it open rather than ticking on the strength of a label.
§1 evidence pass — two ticked, and two showcase defects found & fixed
Driven in the running showcase (
os dev, wiped DB, console rebuilt to the pinnedobjectui@cf2d56e32a11so this isn't testing a stale bundle).Two things weren't merely un-ticked — they weren't running at all:
- fix(showcase): stop passing a non-existent org column when seeding the phone demo user #3408 (merged) — the phone demo persona was never provisioned on any boot. The seed inserted
sys_userwith anorganization_idkey; that field does not exist (not in the 25-field object definition, not in the 26-column table — org membership is onsys_member), and it is not dropped on the way down: it reaches SQL as a real column and the insert dies withtable sys_user has no column named organization_id. A best-efforttry/catchreduced it to one ERROR line in the boot log. §6 "Phone sign-in surfaces" had nothing to show. - feat(showcase): actually exercise the per-group (会签) approval demo #3409 (merged) — the per_group (会签) demo had never opened a request.
showcase_expense_signoffis the only flow authoringbehavior: 'per_group', but the seed never launched it, and nobody heldauditor(the position behind itsfinancegroup), so the group would have resolved empty anyway. Fixed by adding anAda Auditor (demo)persona holding onlyauditor— deliberately a different user from the admin, since one user in both groups satisfies both tallies with a single decision — and launching it on EXP-2001 ($1,500), under the $5,000 committee threshold so the quorum demo doesn't also fire on the same record.
The inbox now carries exactly one request per behavior:
unanimous(INV-1001),quorum(EXP-DEMO),per_group(EXP-2001).Ticked
Server-computed progress — the per_group drawer renders the tally server-side, per group, with the two distinct holders:
Sign-off progress — 0 of 2 groups [finance 0/1] [manager 0/1] 等待以下审批人: Dev Admin · Ada Auditor (demo)and the inbox row for the quorum request reads
Committee Sign-off (2 of 3). That is the "per-group tick badges / N of M · group pending" assertion.Notification deep link —
…/system/approvals?request=areq_b19241c6-…opens the request drawer directly (第 1 / 3 条), verified twice on fresh loads.Not ticked, with what's missing
- Metadata-driven inbox actions — 5 of the 7 render from metadata:
ApproveRejectReassignSend backRequest info.remindandrecallnever appear, and I believe that is correct here rather than a bug: they are submitter-side actions, and every seeded request has an empty submitter (申请人—) because the demo seed launches flows as SYS. Which means… - Viewer gating — …the "as the submitter viewing your own pending request" half is not demonstrable at all today: the 我发起的 tab is empty for every user. Stamping a real submitter on one seeded request would unlock both this and the remind/recall half above. Worth doing.
- Reassign user picker — the dialog's
New approver*is a picker control (选择..., helper "User to hand this step to"), not a free-text id box, which is the substance of the assertion — but I could not get the option list open to confirm it enumerates realsys_userrows, so I'm leaving it. - M-of-N quorum — config verified (
behavior: quorum,minApprovals: 2, three position approvers). But the slate collapses onto one person: the admin holdsmanager+finance+legal, sopending_approvers = <admin>,<admin>,<admin>and "approves once the threshold is met, remaining tasks close" cannot be observed. There's a genuine tension here — admin holding every position is deliberate so a single logged-in user can action the whole inbox, but splitting the positions across three people would also stop theunanimousinvoice demo from being completable solo. That's a showcase design call, so I've left it rather than changing it unilaterally. - Decision attachments — not reached this pass.
Minor, low confidence
The inbox footer advertises
Enter 打开, but pressingjthenEnterhighlighted the row without opening the drawer (URL unchanged, no dialog). Focus may simply not have been where I assumed — flagging rather than filing.- fix(showcase): stop passing a non-existent org column when seeding the phone demo user #3408 (merged) — the phone demo persona was never provisioned on any boot. The seed inserted
§1 continued — 会签 and the full action set now proven end-to-end
Third showcase defect found and fixed on the way: every seeded request had a null submitter (#3411). The demo launches flows as SYS and the approval node stamps the requester from
context.userId, so 申请人 was—everywhere, the 我发起的 tab was empty for every user, and the two submitter-gated actions could never render. Fixed by routinguserIddeliberately: the invoice is submitted by the admin (so the logged-in user owns one request), the other two by Mei Phone (demo), who holds no position and is therefore never one of her own approvers.Ticked
Per-group sign-off (会签) — the decisive run. Approved as Dev Admin, who holds the
managergroup only:before status=pending pending=[Dev Admin, Ada Auditor (demo)] finance 0/1 manager 0/1 after status=pending pending=[Ada Auditor (demo)]One group's approval satisfies that group and drops it from the slate, but does not finalize the request — it keeps waiting on
finance. That is exactly "needs one approval from each group". The contrast is right there in the same session: theunanimousinvoice, whose two slots both resolve to the admin, went straight toapprovedon a single decision.Metadata-driven inbox actions — all 7 render, and are gated by the viewer's relationship to the request rather than hand-wired. Same viewer (admin), two requests:
Request Rendered actions submitted by Mei ApproveRejectReassignSend backRequest infosubmitted by admin those + Send reminder+RecallDecision attachments — dialog confirmed, upload not exercised
Approve opens a real param dialog: Comment + Attachments with a drag-and-drop
input[type=file]carryingmultiple. The comment round-trips (sys_approval_action.comment= "Approved by manager group…"). I did not attach an actual file, so the "uploads and shows as a chip" half is unproven — leaving it unticked.Correction to my previous comment: I suspected the drawer's action buttons were dead, because three coordinate-based clicks on
Approveclosed the drawer without issuing any request. That was my automation, not the product — a ref-targeted click opens the dialog and the decision commits normally. No bug; disregard that note.Quorum — now with a concrete symptom
The collapse I flagged earlier is not cosmetic.
showcase_committee_quorumdeclaresminApprovals: 2over three position approvers, but all three resolve to the admin, and the drawer renders:Approvals — 0 of 1 等待以下审批人: Dev Admin · Dev Admin · Dev AdminThe runtime has clamped the threshold to the number of distinct approvers — sensible as a fail-safe (a 2-of-3 with one real person could otherwise never complete), but it means the demo advertises 2-of-3 and behaves as 1-of-1. M-of-N is still not demonstrable, and fixing it needs three distinct position holders — a showcase design call I'm still leaving to you, since splitting the positions also stops the
unanimousinvoice demo from being completable by one person.Viewer gating — half demonstrated
The submitter-gated direction is proven above (recall/remind appear only on the viewer's own request). The other half — "as the submitter you do not see approver buttons" — remains unreachable, because the admin holds every approver position and is therefore an approver on all three seeded requests. It needs one request routed to a position the admin does not hold.
- added a commit that references this issue
on Jul 22, 2026 §2 Automation — run-step nesting ticked, roll-up filter half-proven
Flow Runs step logs ✔ — ran
showcase_batch_reminders(aloopflow) from the developer Flow Runs page with a 3-itemtasksinput. The engine tags each body step with its container, iteration and region:start — loop_tasks — send_reminder parent=loop_tasks iter=0 region=loop-body send_reminder parent=loop_tasks iter=1 region=loop-body send_reminder parent=loop_tasks iter=2 region=loop-bodyand the flow designer's Runs panel renders that as the execution tree, not a flat list:
run run_f82c33cd-… · trigger manual SUCCESS start START 0ms SUCCESS loop_tasks LOOP 1ms └ ITERATION 1 SUCCESS send_remi… SCRIPT 0ms └ ITERATION 2 SUCCESS send_remi… SCRIPT 0ms └ ITERATION 3 SUCCESS send_remi… SCRIPT 0msWorth noting for anyone else checking this: the nesting lives in the flow designer's Runs panel (
FlowRunsPanel, #1505). The developer Flow Runs page rendersrunDetail.stepsflat — looking there alone would read as a miss.Roll-up summary filter — recompute proven, the editor half is not reachable
The "parent recomputes" half is solid.
showcase_expense_reportdeclares filtered roll-ups in all three filter shapes, and I flipped one $780 child line fromapproved→rejectedover REST:Field Filter shape before → after approved_amountequality status = 'approved'825.0 → 45.0 rejected_countequality COUNT 1 → 2 total_amountnone 917.0 → 917.0 (correctly unmoved) reimbursable_amountboolean billable825.0 (correctly unmoved) over_limit_countoperator amount $gte 5001 (correctly unmoved) Every value matches an independent recomputation from the child rows, and only the filters the edit actually touched moved — which is the part a naive "recompute everything" implementation would get wrong.
What I could not do is the other half — "set a child-row filter via the visual editor". The showcase ships as a read-only package (Studio shows the 只读 badge and "只读软件包 — 请切换或新建可写软件包后再编辑"), so no field on it can be edited in Studio at all. Exercising the visual filter editor needs a writable package with a
summaryfield in it; that's a fixture gap, not a product finding, so I've left the item unticked rather than ticking it on the recompute half alone.§3 blocked by a showcase data defect — filed as #3415
While setting up the Report drill-through range check I found the showcase has 1 project and 1 task at runtime, against 5 and 10 in the built artifact.
Root cause:
showcase_projectdeclaresinitialStates: ['planned']withevents: ['insert','update'](the #3165 FSM entry gate), but the project seed creates projects asactive / active / on_hold / planned / completed. Seeds run validation on purpose (SEED_OPTIONS— "Lifecycle HOOKS … validation still run"), so four are correctly rejected; the 9 tasks that master-detail onto those dead projects go with them, andshowcase_project_membershiplands 0 of 3.Nothing reports this.
SeedLoaderwarns per rejection, but not one[SeedLoader]line reachesos devoutput even atOS_LOG_LEVEL=debug LOG_LEVEL=debug DEBUG=*.Consequences for this checklist:
- Report drill-through range — not checkable. Both month-bucketed trend widgets have a single point; there is no date bucket to drill into. Blocked on showcase: 4/5 projects and 9/10 tasks are silently rejected on every boot (FSM initialStates vs seed data) #3415.
- Chart first paint (already ticked ✔) — the paint itself is fine, but be aware all 14 Chart Gallery widgets are drawing one data point, so that tick says less than it looks.
- Kanban / gantt / timeline / calendar / map demos are all single-item for the same reason.
Dataset-only widget authoring is also not checkable in this environment for an unrelated reason: the showcase ships as a read-only package (Studio shows 只读 and "只读软件包 — 请切换或新建可写软件包后再编辑"), so no widget inspector on it can be opened for editing. Same blocker as the roll-up filter editor above.
- added a commit that references this issue
on Aug 7, 2026 - added a commit that references this issue
on Aug 25, 2026 - added a commit that references this issue
on Sep 1, 2026
Centralized browser-based verification of the entire 16.0 release. Each item is a shipped 16.0 capability with a concrete path to exercise it and the expected result. Source of truth:
content/docs/releases/v16.mdx(PRs #3322 / #3331 / #3357).Environment
dogfood-verificationskill). Showcase seeds make most features demonstrable out of the box.osCLI or an API panel ·1. Approvals 🖥️
quorum+minApprovals; submit; confirm it approves once the threshold is met, and that a single rejection still vetoes.per_groupwith 2 named groups (e.g. manager + finance); confirm it needs one approver from each group; try the showcaseExpenseSignoffFlow.?request=<id>opens the request drawer directly.sys_userpicker (not a free-text id box).2. Automation / Flow (Studio) 🖥️
timeRelative(offsetDays: [60,30,7]orwithinDays); confirm the first-class designer panel; verify it fires per matching record on the daily sweep.loop/parallel/try_catchnode shows its nested region inline (not an opaque card); select and edit a node inside a region through the schema-driven inspector.{var:value}map renders akeyValueeditor (not raw JSON); a number array rendersnumberList.summaryfield; set a child-rowfiltervia the visual editor; confirm the parent total only counts matching children.os dev/serve, a misauthored auto-launched flow surfaces⚠lines in theFlows:startup banner and ERROR logs on a failed trigger run.3. Dashboards & Analytics 🖥️
dataset+dimensions+values.categoryField); confirm a loud parse error naming the key + pointing at the dataset shape (was: silently renders nothing).4. Records / Lists / Detail / Forms 🖥️
multiple/accept/maxSize).autonumberparam maps to the AutoNumber widget.visibleWhenparity — a dependent select/multiselect/radio/checkboxes option shows/hides as its parent changes (cascading +dependsOn);select + multiplerenders a multi-value chip picker.$top/$skip(doesn’t load every row).owner_idstays out of leading columns and lands in the detail meta section.5. Access / Studio pillar 🖥️
record.visibleverdict with the deciding layer.systemobjects distinguished fromengine-ownedin badges + empty states.6. Auth / Login / Identity 🖥️
ssoEnforcedon first paint (no password wall flash); a hung sign-in recovers via watchdog.os devlogin page shows the dev-seeded admin credential hint.7. i18n 🖥️
collab.assignmentbell notification title is localized (recipient locale), opens a localized detail page, and mark-as-read clears the unread state on standalone/os dev(the notifications REST routes are now mounted).sys_filedetail page labels + the Pending/Committed/Deleted status pipeline render in the workspace locale.8. Gantt (plugin-gantt) 🖥️
beforeTaskUpdatehost veto blocks a bad drag;dependencyTypesswitch hidden for id-only stores.9. Backend / API / CLI (indirect) 🔌
organizationIdin hooks/actions —ctx.user.organizationId/ctx.session.organizationIdresolve;tenantIdalias removed.OS_MCP_STDIO_API_KEYfails closed; with a valid key, reads honor RLS/FLS.client.capabilities.transactionalBatchreflects whether/batchis mounted + transaction-capable.readonlyfields stripped on INSERT+UPDATE via REST;owner_idforge/transfer denied; cross-object/batchgated by per-object API rules; bulk-update runs validation rules.record.due_date == today()matches;cond ? x : null+floor/ceilwork; date arithmetic (end - start + 1) is now a build-time error inos build.visibleWhenenforced server-side — a crafted write of a gatedcheckboxesoption is rejected.events:['delete'], a webhookundelete/apitrigger, oraiStudio/aiSeatfails at parse/build.OS_SERVER_TIMING/X-OS-Debug-Timing, an admin sees auth/db/hooks/serialize spans; a non-admin does not.validate_expression— an agent validating a formula against an object schema gets errors/warnings/inferred type.Notes
v16.mdx.⚠️items are regressions/holes fixed in 16.0 — the strongest test is to reproduce the old broken behavior and confirm it no longer happens.