Skip to content

Commit 1ee7e4f

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-9szbaq
2 parents 67663ae + f6a04e3 commit 1ee7e4f

85 files changed

Lines changed: 4236 additions & 599 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/plugin-list': patch
3+
'@object-ui/plugin-tree': patch
4+
---
5+
6+
fix(ui): a tree list view draws the view's authored empty state, and the tree's load failure is translated
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
- **Empty tree views.** A list view shown as a tree now draws the same empty state as the grid and the gallery: the view's authored `emptyState` (title, message, icon, link) first, otherwise the localized "Nothing here yet" copy, with the same Create, Clear search and Clear filters actions. It used to draw the tree's generic "No data" line, so guidance authored on the view was lost on that layout. The list toolbar stays above it.
12+
- **Tree load failures.** A tree whose read fails shows the translated "Couldn't load records" title with the error's own message under it, in the alert panel the list uses, instead of the English line "Failed to load tree: …" on every page.
13+
14+
No metadata key or option is added.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/service-analytics': patch
3+
---
4+
5+
fix(service-analytics)!: the /analytics/sql dry run refuses a date bucket over a column that is not a date or datetime, as the query does, instead of rendering it
6+
7+
Clause-②: no (narrowing) — `/analytics/sql` (`AnalyticsService.generateSql`) now refuses `INVALID_FIELD` / 400 at `groupBy.N.dateGranularity` a time-dimension granularity over a field declared neither a date nor a datetime (text, select, number, boolean, time, …), with the refusal `/analytics/query` gives the same selection, field for field; it rendered a statement for it (or, at a non-UTC zone, answered `NOT_IMPLEMENTED` / 501 saying the query is served). Nothing is widened: no export, public member, answer member, error code or status is added.
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type of the package root changes: a dry run that rendered a statement the query refuses is refused with the query's refusal, already on the wire. There is nothing for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** accept-set narrowing on the `/analytics/sql` dry run (`@objectstack/service-analytics`). `/analytics/query` is unchanged.
13+
14+
- **Before.** Measured on real SQLite on `main` `ab085a04`. `/analytics/query` refused a month bucket over a text, select or boolean column, a year bucket over a number and a day bucket over a `time` column with the engine's `INVALID_FIELD` / 400 at `groupBy.N.dateGranularity`. The dry run rendered `strftime('%Y-%m', region)` and its kin, on the direct and the cross-object path, and for a cube dimension's one declared granularity asked raw. At `America/New_York` it answered `NOT_IMPLEMENTED` / 501.
15+
- **Now.** The dry run judges the keys `execute()` hands the engine (its cross-object path's when the selection routes there) with the reading the draft preview already used (#28852), now `dataset-date-bucket-door.ts`. It runs after the dry run's `dateRange` read and its column-asked-twice refusal, as `query()` refuses, and gives the engine's refusal: the same message, `object`, `param`, `path`, `field` and `fields`. A field with no declared type, or a `formula`, is not judged, as in the engine.
16+
- **Unchanged.** A date or datetime bucket renders byte for byte, the statement the query echoes.
17+
- **Who sees it.** A caller of the dry run for a selection the query refuses: the query's refusal, instead of SQL for a statement that never runs.
18+
19+
**The remedy.** Group by the column without a granularity, or bucket a date or datetime field.
20+
21+
Also in this change, the draft preview of `queryDataset` lowers its row filters (the `where`, then the time-dimension windows) once, before its column-asked-twice and date-bucket refusals, where the ObjectQL strategy lowers them. So a selection carrying an unrecognised `dateRange` beside one column asked twice or beside a bucket over a non-date column is refused about the `dateRange` (`ANALYTICS_DATE_RANGE_UNRECOGNIZED` / 400) on every face, and a refused filter beside a non-date bucket is refused about the filter. The preview answered the other refusal.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): the record-page `timeline` total is documented as an upper bound of the entries the 动态 panel draws
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The `RecordPageTimelineRequestSchema` docblock, and the record-page file header that the API reference page is generated from, now say what the `timeline` total counts: the sources' rows as the schema's table defines them. That makes it an upper bound of the entries the 动态 panel draws, not their number. The panel draws fewer where its `showCompleted` / `types` configuration filters entries out, and where a ledger row has no displayable diff. Prose only: no schema shape, `.describe()` text or behaviour changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime): a flow clone the engine refuses no longer installs the source's empty `.custom` customization package
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`POST /api/v1/automation/:name/clone` without `package` lands the copy in the source's paired customization package `<base>.custom`, ensuring it if needed. The pair used to be ensured before the engine validated the copy, so a clone refused `400` for an illegal machine name (or any other definition the engine refuses) still left a newly installed, empty `<base>.custom` in the package list and in Studio. The pair is now ensured only after the engine has accepted the copy, so a refused clone changes nothing. Refusals that can only come later — the ensured pair judged not writable, or the store refusing the save — still leave the pair in place, which is its ordinary state: ensure is idempotent, an empty pair is valid, and the next clone reuses it.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/console': patch
3+
---
4+
5+
fix(console): a refused form submit says a localized sentence, never the server's response body
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The Console form page (`/forms/NAME`, the in-app form and the public `/f/SLUG`) drew a refused write as the verb, the status and the raw response body, so a member without create rights read `Create failed (403): {"error":…,"code":"PERMISSION_DENIED","object":…}` in English on the banner and the toast. The page now reads the refusal the way the record dialog does, and the banner and the toast say the same sentence:
11+
12+
- a sentence the producer marked for the end user (`userMessage`), or a delegated administrator's scope refusal, as written;
13+
- any other denial (`403`, `PERMISSION_DENIED`, `FORBIDDEN`) as the localized "You don't have permission to save this record.";
14+
- the per-rule lines no input carries, one per line, as before;
15+
- the server's own sentence for any other `4xx`, read out of the envelope (`error`, or the wrapped `error.message`), never the body itself;
16+
- a `5xx`, or a refusal with no sentence (a proxy's markup page), as the localized "Could not save. Please try again."
17+
18+
The fields a refusal names are still marked under their inputs. No i18n key is added: both lines are the existing `form.noPermissionToSave` and `form.submitFailed`, present in all ten packs.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/plugin-list': patch
3+
---
4+
5+
feat(plugin-list): the list's empty-state "Clear filters" also clears the user-filter chips, so a list emptied by a chip alone offers it
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
A list emptied by a user-filter chip alone (the quick-filter chips above the grid) said "No records match your current filters or search" and offered only "Add record": the chip kept its selection inside `UserFilters`, out of the list's reach. It now offers "Clear filters" as its primary action, as a list emptied by the filter panel or the "In a queue" toggle does, and one click clears the chips with the rest of the reader's query.
11+
12+
- **`UserFilters` takes an optional `ref`** that receives a `UserFiltersHandle` (exported from `@object-ui/plugin-list`) while a dropdown or toggle bar is mounted. Its `clear()` empties every chip, author defaults included, exactly as pressing each chip's × would: `onFilterChange` receives no conditions, and `onSelectionsChange` reports every chip field with an empty selection, so a host that persists selections per field (the console's `uf_*` URL params) drops each one. The selection stays the bar's own state, and the bar is not remounted. A host that passes no ref gets the bar unchanged.
13+
- **Preset tabs are not cleared.** A tab is one of the view's presets, not a filter the reader added: tabs mode sets no handle, and a list a tab empties still offers no clear action.
14+
- No metadata key, option or copy changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@object-ui/console': patch
3+
'@object-ui/app-shell': patch
4+
---
5+
6+
fix(console): the approvals flow works at phone width — 44 px controls, and a long inbox stays responsive
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
The mobile app hosts the Console in a WebView, so a phone approver gets the Console at 375 px. Measured in Chromium at 375×812 and 390×844 on the showcase and CRM examples:
12+
13+
- **Touch targets.** Below 768 px the approvals inbox's tabs, search field, filters, the phone cards' Approve / Reject and the inline approve / reject confirmations' buttons take a 44 px height (Button's `lg` size, `h-11` where a control has no size); they measured 28–40 px. The record page approval panel's Send reminder does too (it was 24 px). A desktop keeps every size it had.
14+
- **No zoom on focus.** The inbox search field keeps the 16 px text inputs use on a phone, so focusing it no longer makes iOS zoom the page.
15+
- **A long inbox stays responsive.** The inline confirmations now hold their own row and Reject reason, the rows are memoised, and the pending list follows the search box at a deferred priority. At 200 pending requests on a phone at 4× CPU, typing a 20-character reject reason went from 8.4 s to 0.5 s, opening a confirmation from about 1 s to 0.3 s, and the longest task while deciding or searching from about 850 ms to 120 ms.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): on a phone the navigation drawer closes when an entry opens its page, and the top bar's controls and menus are 44 px touch targets
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The Console at phone width (375 and 390 px), as a WebView host shows it:
11+
12+
- **The drawer closes on navigation.** Below 768 px the sidebar is a drawer over the page. Tapping one of its entries used to open the page under a drawer that stayed open, so every navigation cost a second tap. A route change now closes the drawer, whichever entry (navigation, favorite, recent) or other control changed the route. Wider windows are unchanged.
13+
- **44 px controls below `md`.** The top bar's sidebar trigger, product mark, app pill, search, Studio button, inbox bell, AI button and account avatar measured 28 to 34 px; they are 44 px below 768 px. So are the theme and language buttons in the account menu and the page header's Refresh button. The rows of the app switcher, account, theme and language menus are 44 px tall there too (they were 32 and 38 px).
14+
- **A roomier phone bar.** Below 640 px an app's bar no longer draws the product mark (and its `/` rule) or the Studio button, which made room for the 44 px controls and the page title. Both stay one tap away: the app switcher's menu has "Back to home" and the Studio row, and the drawer's "All apps" goes home. Home and the other platform surfaces keep the product mark.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/plugin-dashboard': patch
3+
'@object-ui/app-shell': patch
4+
'@object-ui/components': patch
5+
---
6+
7+
fix(console): dashboards meet the 44 px touch-target floor at phone width
8+
9+
Clause-②: no
10+
Metadata-protocol: none
11+
12+
Measured on a real Console at 375 and 390 px (objectstack#29078, part of the #29040 phone-width audit), the Dashboards flow had no horizontal page scroll, every overlay fit and scrolling stayed smooth at 4× CPU throttling, but most of its controls were 16 to 36 px tall. Below `sm` they are now 44 px touch targets; from `sm` up nothing changes:
13+
14+
- the dashboard page's Refresh All;
15+
- the filter bar's date, select and text controls, its Reset, and the option rows of its lists;
16+
- the custom-range calendar, which opens one month with 44 px days below `md` (two stacked months at that size ran past the viewport);
17+
- the dataset table's sort buttons and drillable rows, the pivot's drillable cells, and Export CSV, which on a phone takes its own row above the table instead of covering the last column's sort button;
18+
- the drill drawer's Open in list, and the Close button of every Sheet.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/console': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(console): the settings hub tells a member that an administrator manages the settings
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
- **A member is no longer shown a developer sentence.** `GET /api/settings` lists only the manifests the caller may read, and a manifest that declares no `readPermission` reads at the spec default, `setup.access`. A plain member opening the settings hub under an app they may use was answered an empty list and read "No settings registered. Plugins can register settings manifests via the SettingsService." The hub now asks the capability answer the Console already holds (`/auth/me/permissions`, through `MePermissionsProvider`; no new request): a caller without `setup.access` reads "Settings are managed by your administrator" with a line saying to ask an administrator, in the style of the app-level "You don't have access" screen.
12+
- **The empty message for an administrator is reworded out of developer language:** "No settings pages are available yet. They appear here when an installed app provides them." A caller with manifests sees the list unchanged. An unreported capability answer fails open onto this message, as every capability gate does.
13+
- **New strings** `console.settingsHub.adminManaged` and `console.settingsHub.adminManagedDescription`, and the reworded `console.settingsHub.empty`, in all ten locale packs. The two new keys widen the exported `en` catalogue and `TranslationKeys` type; nothing is removed.

0 commit comments

Comments
 (0)