Skip to content

Commit f15bb82

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-14477-inert-deadline-key-retirement
2 parents 3c7b84d + e601c04 commit f15bb82

26 files changed

Lines changed: 3558 additions & 229 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
'@objectstack/lint': minor
3+
---
4+
5+
Two new widget-binding rule ids for a chart widget with an empty selection
6+
7+
`validateWidgetBindings` reported nothing about two dataset-bound chart shapes that the
8+
`@object-ui` revision this repo pins (`.objectui-sha`) visibly degrades. Both are now
9+
warnings, suppressible per widget with `suppressWarnings: ['<rule-id>']`:
10+
11+
- `chart-measures-missing` — a chart-family widget selects no measures (`values` empty or
12+
absent). `DatasetWidget.tsx:683` returns the authoring placeholder "Pick measures
13+
(values) for this dataset widget." before any query runs, above every family branch, so
14+
no chart is drawn at all.
15+
- `chart-dimensions-missing` — a chart-family widget selects at least one measure but no
16+
dimensions. `DatasetWidget.tsx:423` reads
17+
`const isMetric = METRIC_TYPES.has(widgetType) || dimensions.length === 0;`, so the
18+
widget renders as a single KPI number and the declared chart family is silently ignored.
19+
The hint steers the author to a dimension, or to the `metric`/`kpi` family that matches
20+
what actually renders.
21+
22+
Warning tier rather than error for both: an empty selection is a work-in-progress state a
23+
build must tolerate, and erroring would gate the `sys_metadata` publish path on a
24+
half-authored widget. Neither shape is folded into `chart-config-missing` — neither is
25+
caused by, nor repairable with, `chartConfig`, which carries presentation only.
26+
27+
"Chart family" is derived, not hand-listed: every declared `ChartTypeSchema` option that
28+
the pinned renderer routes to its chart branch — the taxonomy minus the renderer's own
29+
`METRIC_TYPES` (`metric`, `kpi`, `gauge`, `solid-gauge`, `bullet`) and its `table`/`pivot`
30+
tabular test. A `metric` tile with no dimensions, such as the shipped `system_overview`
31+
board's own KPI tiles, is therefore not a finding.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
"@objectstack/spec": patch
3+
"@objectstack/platform-objects": patch
4+
---
5+
6+
fix(spec): the dashboard `gap` field no longer describes itself to app authors in Tailwind vocabulary
7+
8+
`ui/dashboard`'s `gap` key told app authors its value in the vocabulary of a CSS
9+
library they never chose and cannot act on. **Two** independent producer strings
10+
carried that wording, and they feed two independent customer-facing surfaces:
11+
12+
- `dashboardForm`'s `helpText``Grid gap (Tailwind units)` — rendered verbatim in
13+
the Studio property panel, which is spec-driven and feeds this form straight into
14+
the generic form renderer.
15+
- `DashboardSchema.gap`'s `.describe()``Grid gap in Tailwind spacing units`
16+
rendered as this field's row in the published reference page
17+
`content/docs/references/ui/dashboard.mdx`. The reference corpus renders
18+
`.describe()`, never `helpText`.
19+
20+
Both now read **Space between widgets, in steps of 0.25rem (4 = 1rem)**: what the
21+
author decides, plus the magnitude, stated in a CSS unit instead of a framework's
22+
scale. The magnitude had to survive the rewrite rather than be dropped with the
23+
framework name — the number is a spacing step, so `4` means `1rem` and not `4px`,
24+
and an author who lost that would come away knowing less than before.
25+
26+
The step size is stated as measured rather than inferred: the dashboard renderer
27+
sets the grid gap as an inline style computed from this key, so every accepted
28+
value is linear and one step is exactly `0.25rem`. "Tailwind units" was doubly
29+
wrong — it named an implementation dependency, and it named one the consumer of
30+
this key does not have.
31+
32+
**No schema change.** `gap` stays `z.number().int().min(0).optional()` and accepts
33+
exactly what it accepted before; nothing is added to or removed from any public
34+
surface. `columns` is deliberately untouched on both of its producer lines —
35+
`12` is an author-visible fact about the grid being laid out, not a framework
36+
detail — and this is one field's two strings, not a sweep for framework words.
37+
38+
The `en` metadata-forms translation bundle is a mechanical copy of the form source,
39+
so it is regenerated to match. Translated locales are not touched: regeneration
40+
fills gaps only and never overwrites an existing leaf.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/cli": patch
3+
---
4+
5+
`os generate migration` gives a table's own `id` column the shape the platform actually creates.
6+
7+
Both migration generators hardcoded the primary key as a UUID — `"id" UUID PRIMARY KEY DEFAULT gen_random_uuid()` in the SQL format, `table.uuid('id').primary().defaultTo(db.fn.uuid())` in the TypeScript one (the default format). The platform's SQL driver emits `table.string('id').primary()`, which is knex's `varchar(255)`. A platform id is a string, not a uuid, so on Postgres the generated table refused the platform's very first insert with `22P02 invalid input syntax for type uuid`.
8+
9+
The quieter half is the `DEFAULT`, and it is why this was worth correcting rather than working around. The driver emits no database-side default at all — its insert path always supplies the id itself — so `gen_random_uuid()` never fired for a platform write, only for an out-of-band one, handing that row a 36-character uuid this platform's id generator would never mint. One table would then hold two incompatible id shapes, with nothing said.
10+
11+
Both generators now emit the driver's own answer: `"id" VARCHAR(255) PRIMARY KEY` and `table.string('id').primary()`. The correction also closes a contradiction inside the generator file, whose prose already stated that a reference column takes the width of the target's `id` column *because* the driver emits `table.string('id').primary()` — a few hundred lines above the two lines that emitted `uuid`.
12+
13+
`generate-builtin-id-column.pin.test.ts` reads the width from the driver's own `DEFAULT_STRING_VARCHAR_CHARS` rather than transcribing `255`, so the generators cannot drift away from the driver again without a named failure.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/objectql": patch
3+
---
4+
5+
The platform-object tenancy census is derived and gated instead of hand-written in a comment. Documentation only — no runtime behaviour changes.
6+
7+
`PLATFORM_OBJECT_TENANCY`'s header explained why the reclassification needs a ledger rather than a schema read, and backed the argument with three hand-written digits and a parenthetical attributing them. Nothing re-derived any of it, so it was true only until the population moved and failed silently when it did — in both of the directions a prose count can.
8+
9+
The parenthetical mis-attributed the exclusion: it named `sys_sso_provider`'s `tenancy.enabled: false` as an addition to the `managedBy: 'better-auth'` set that object was already in, and left `sys_api_key`'s identical opt-out unnamed. The arithmetic stayed right, which is why no reader and no gate caught it — a wrong reason producing a right total is the shape that survives longest. The digits then went stale when an object opted out of the tenant column through a third mechanism the parenthetical's taxonomy had no slot for (`systemFields: { tenant: false }`), while the gated page next door was updated in the same commit.
10+
11+
The digits and the parenthetical are deleted rather than corrected. The header now points at `scripts/platform-object-tenancy-census.json` and states the PREDICATE it was missing: an object is inside the machinery when `resolveTenantFieldName` answers non-null on the **registered** schema — after `applySystemFields` has injected the tenant column, because the injected column is what the engine sees, not what the author typed. Counting `managedBy` as if the resolver read it is the mistake that produced the wrong reason.
12+
13+
The artefact is derived by `scripts/platform-object-tenancy-census.mjs`, which loads `resolveTenantFieldName` and `resolveInjectedSystemColumns` from source and executes them rather than re-spelling what they decide, and is held to the tree by `scripts/check-platform-object-tenancy-census.mjs`. It records per object the declaration on that object's own schema that puts it outside the reach; declarations are not mutually exclusive and an object carrying two keeps both. An excluded object with no declared mechanism is an error, not a default: the generator refuses to commit the row and the gate reds, so a new exclusion mechanism is adjudicated rather than absorbed into an existing total.

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,4 @@ content/docs/references/** merge=os-regen
154154
content/docs/permissions/system-context.mdx merge=os-regen
155155
skills/*/references/_index.md merge=os-regen
156156
skills/objectstack-ui/references/react-blocks.md merge=os-regen
157+
scripts/platform-object-tenancy-census.json merge=os-regen

.github/workflows/lint.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,54 @@ jobs:
15931593
node scripts/check-tenant-audit-census.mjs --self-test
15941594
node scripts/check-tenant-audit-census.mjs
15951595
1596+
# #14957 platform-object tenancy census.
1597+
# `scripts/platform-object-tenancy-census.json` carries which
1598+
# platform-namespace objects the tenancy machinery can REACH, and the
1599+
# declaration on each excluded object's own schema that puts it outside.
1600+
#
1601+
# ⭐ It exists because the same census was PROSE, in the header of
1602+
# `packages/objectql/src/tenancy/platform-object-tenancy.ts`, and failed in
1603+
# both available directions inside one month. First a wrong REASON behind a
1604+
# right total: the parenthetical named `sys_sso_provider` as an addition to
1605+
# the `managedBy: 'better-auth'` set it was already in, and `24 + 1 = 25`
1606+
# stayed right, which is why neither a reader nor a gate caught it — that
1607+
# count reached a PR body and a filed card, and two independent
1608+
# re-measurements were spent proving a correct file correct. Then a stale
1609+
# TOTAL: PR #15155 declared `systemFields: { tenant: false }` on
1610+
# `sys_metadata_activation`, the object left the machinery's reach, and the
1611+
# ungated prose kept the old digits while the same commit updated the GATED
1612+
# page next door. CI was green throughout both.
1613+
#
1614+
# ⚠️ The PREDICATE is the deliverable and the number is a by-product:
1615+
# `resolveTenantFieldName` answering non-null on the REGISTERED schema
1616+
# (after `applySystemFields` injects the tenant column). A gate that
1617+
# re-derives the number without stating the predicate would recreate the
1618+
# same trap with fresher digits, and `managedBy` — the thing the prose
1619+
# counted as if the resolver read it — is not the predicate.
1620+
#
1621+
# ⭐ Its third verdict is the one worth having: an object outside the reach
1622+
# with NO declared mechanism to explain it is an ERROR, not a default. That
1623+
# is what fires on a new exclusion mechanism the day it lands, instead of
1624+
# letting it be absorbed into an existing bucket's total.
1625+
#
1626+
# It is a DRIFT gate, not an anchor gate: the artefact carries no line
1627+
# numbers, so displacement cannot move it, and there is exactly one
1628+
# mechanical repair path (`node scripts/platform-object-tenancy-census.mjs
1629+
# --write`). It lives in this job, with no paths filter, because the PRs
1630+
# that move this population are code-only ones that add or re-declare an
1631+
# object. The `--self-test` invocation is what `check:self-test-wired`
1632+
# requires and is the only instrument on this gate's two matching rules;
1633+
# a clean tree cannot tell a working rule from a weakened one.
1634+
#
1635+
# Invoked as `node` rather than through a `pnpm check:*` alias: see the
1636+
# GATE INVOCATION IDIOM note at the top of this file. TypeScript transpile
1637+
# of the predicate's import graph plus every tracked object declaration,
1638+
# no build and no spawns; ~10s.
1639+
- name: Platform-object tenancy census matches the tree
1640+
run: |
1641+
node scripts/check-platform-object-tenancy-census.mjs --self-test
1642+
node scripts/check-platform-object-tenancy-census.mjs
1643+
15961644
# #12236 one <h1> per doc page. `DocsTitle` renders the frontmatter `title`
15971645
# as the page's <h1> unconditionally (apps/docs/app/[lang]/docs/[[...slug]]/
15981646
# page.tsx), so a body-level `# ` heading compiles to a SECOND one inside

content/docs/references/ui/dashboard.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const result = DashboardSchema.parse(data);
3333
| **header** | `{ showTitle: boolean; showDescription: boolean; actions?: object[] }` | optional | Dashboard header configuration |
3434
| **widgets** | `{ id: string; title?: string \| Record<string, string>; description?: string \| Record<string, string>; type: Enum<'bar' \| 'horizontal-bar' \| 'column' \| 'line' \| 'area' \| 'pie' \| 'donut' \| …>; … }[]` || Widgets to display |
3535
| **columns** | `integer` | optional | Number of grid columns (default 12) |
36-
| **gap** | `integer` | optional | Grid gap in Tailwind spacing units |
36+
| **gap** | `integer` | optional | Space between widgets, in steps of 0.25rem (4 = 1rem) |
3737
| **refreshInterval** | `number` | optional | Auto-refresh interval in seconds |
3838
| **dateRange** | `{ field?: string; defaultRange: Enum<'today' \| 'yesterday' \| 'this_week' \| 'last_week' \| 'this_month' \| 'last_month' \| …>; allowCustomRange: boolean }` | optional | Global dashboard date range filter configuration |
3939
| **globalFilters** | `{ name?: string; field: string; object?: string; label?: string \| Record<string, string>; … }[]` | optional | Global filters that apply to all widgets in the dashboard |

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@
157157
"check:stall-guard-headroom": "node scripts/measure-stall-guard-headroom.mjs --self-test",
158158
"check:swallow-census-controls": "node scripts/measure-durability-swallow-family.mjs --self-test=gated",
159159
"gen:system-context-census": "node scripts/check-system-context-census.mjs --fix",
160-
"check:system-context-census": "node scripts/check-system-context-census.mjs --self-test && node scripts/check-system-context-census.mjs"
160+
"check:system-context-census": "node scripts/check-system-context-census.mjs --self-test && node scripts/check-system-context-census.mjs",
161+
"gen:platform-object-tenancy-census": "node scripts/platform-object-tenancy-census.mjs --write",
162+
"check:platform-object-tenancy-census": "node scripts/check-platform-object-tenancy-census.mjs --self-test && node scripts/check-platform-object-tenancy-census.mjs"
161163
},
162164
"keywords": [
163165
"objectstack",

0 commit comments

Comments
 (0)