Skip to content

Commit 92eee4d

Browse files
committed
Merge origin/main into claude/issue-18879-retired-version-default-prose
Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
2 parents 0ea26d3 + 02f8e45 commit 92eee4d

15 files changed

Lines changed: 650 additions & 52 deletions
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`collectFlowGraphs` no longer hands out a `FlowGraph` whose `edges` can hold a non-record — the sibling list #16752's repair did not reach (#18102).
6+
7+
`FlowGraph.edges` is declared `readonly FlowEdgeParsed[]`. The walk forwarded it untouched, four lines from the node-side member filter the same walk has carried since #16752, and a nested region's edge list is admitted on `Array.isArray` alone — which proves the LIST and never its MEMBERS. A YAML `edges:` list item left empty deserialises to `null`, and a region its own schema refused is left RAW for `validateControlFlow` to name, so the producer handed out an array holding a member its own declared element type excludes. Measured on `main`:
8+
9+
```
10+
collectFlowGraphs({ nodes: [start, loop{ body: { nodes: [], edges: [null] } }], edges: [] })
11+
graph[1] scope="loop 'lp' body" edges=[null] declared readonly FlowEdgeParsed[]
12+
```
13+
14+
- **The junk member is DROPPED, per list**, through the same one predicate the node side uses (`isRegionDict`), so the two lists the walk hands out cannot drift from each other. Copy-on-write per list: a well-formed flow is handed back the very same arrays.
15+
- ⭐ **The real edge beside it is still HANDED OUT**, and so is the node list. "No non-record members" is half a contract — a filter that emptied `edges`, or reached into `nodes`, would satisfy it. Both are pinned.
16+
- **This is a drop in the producer, not a refusal.** No authoring door's accept set moves: `FlowSchema.safeParse` still returns an envelope rather than throwing, the region `safeParse` refusal in `validateControlFlow` still owns and still reports the malformed region, and `FlowGraph.path` still indexes the RAW node list so a Zod issue stays anchored where the author wrote it. ⛔ Not a looser signature either — the declared element type is unchanged and is now true.
17+
- **Latent, not live — measured, and not for the reason the filing gave.** There are THREE `graph.edges` consumers on the tree, not two. The two in `packages/lint` coerce through `recordsOf` (#16910). The third is `packages/services/service-automation`'s registration pass, which reads `.id` / `.source` / `.target` straight off each member with no guard, and is shielded only by call ORDER — `validateControlFlow` refuses the malformed region a few frames earlier in `registerFlow`. So no throw is reachable today, by one belt more than was counted. After this change the declared type carries it, and the next consumer needs neither a coercion nor a call-order argument.
18+
- **`analyzeRegion` is not one of those consumers.** It throws a `TypeError` on a `null` / `undefined` edge member (measured), but nothing routes producer output into it: its in-repo callers hand it post-`safeParse` region data. It reads an edge list, it does not read `FlowGraph.edges`.
19+
- **No behaviour changes on well-formed metadata.** The only input whose handling moves is input whose declared type already said it could not exist.
20+
21+
Clause-②: no
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
`ListMapConfigSchema` now declares `style` — optional `z.string()`, the map style URL the renderer already reads and the schema refused by name (#18406). In the same stroke `object-map`'s `map` prop points at `ListMapConfigSchema` again, retracting the `z.unknown()` that the missing key had forced.
6+
7+
`ListMapConfigSchema` is a `strictObject`, and `style` was the one member of the renderer's own documented config surface it omitted. Measured at the `.objectui-sha` pin `53ded82b`: objectui's `ObjectMapConfigSchema` (`packages/types/src/zod/objectql.zod.ts:562`) declares all eight keys, `getMapConfig` reads `schema.mapStyle || schema.map?.style` (`packages/plugin-map/src/ObjectMap.tsx:365`), and objectui's own `content/docs/plugins/plugin-map.mdx:131` documents `style` inside the block. `ListMapConfigSchema.safeParse({ style: 'https://tiles.example/style.json' })` answered `success: false`, so a map style could not be declared through the spec's list-view face at all. Declared here under the director seat's decision batch #153 item 4 letter 1, confirmed by the maintainer verbatim 「其他同意」.
8+
9+
**Clause-②: yes (widening)** — one new declared key on a published, strict accept set, so the set a consumer writes against grows. Nothing previously admitted is refused, and nothing is retired. Contract-review tier.
10+
11+
- **`style`, not `mapStyle`, and not both.** Mapbox and MapLibre both call a style URL `style`, and that is the name the renderer reads inside the config block. The competing spelling — objectui#5017's dev warning teaching `map: { mapStyle }` — is corrected on the objectui side rather than learned here, and no alias is declared: an alias would be a permanent obligation for a key nobody has written yet.
12+
- **Not the node-level `style`.** A component node's `style` is `BaseSchema.style`, an inline CSS record; the renderer stopped reading a top-level `style` as a map style at objectui#5017. The component-level `mapStyle` prop is unchanged and still wins when both are present.
13+
- **`object-map.map` stops being `z.unknown()`.** That posture existed only because pointing the door at a schema missing `style` would have refused a value the renderer honours. With the gap closed, the door takes the spec's own block — so a misspelling inside an authored `map` block is now refused at `map`, by name, instead of passing through an open value. The two pins that recorded the divergence are inverted in the same change.
14+
- **The generated projections move with it** — `authorable-surface/ui.json` gains `ui/ListMapConfig:style`, and `content/docs/references/ui/view.mdx` plus `content/docs/references/ui/component.mdx` gain the key; the `object-map.map` row in the component reference changes from `any` to the block's real shape and gains a nested-shape table.

‎.changeset/client-get-session-envelope-and-refresh-read.md‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,21 @@ inert as it was.
5757
`token.signature` form interchangeably, so a client that held the signed form
5858
stays signed in across the call.
5959

60-
Two answers stay outside the declared type and are **not** addressed here: the
61-
anonymous `null`, which would need the published return annotation to widen, and
62-
`SessionUser.image`, declared `z.string().optional()` against a route that
63-
serves `null` (#17235). The sibling `auth.login` / `auth.register`, which
64-
normalize into `data` but set no `success`, are #17234.
60+
Three answers sat outside the declared type when this change was written and
61+
are **not** addressed by it. Each has since been answered on its own card, so a
62+
caller reading this entry does not have to code around any of them:
63+
64+
- the **anonymous** `/get-session` answer, recorded above as `200 null`. It no
65+
longer needs the published return annotation to widen, because the producer
66+
moved instead: since #17881 `plugin-auth`'s `refuseAnonymousSession` converts
67+
better-auth's `200` plus the literal JSON `null` into the declared ADR-0112
68+
refusal — HTTP `401` with `code: UNAUTHENTICATED` — before it leaves the
69+
process. The SDK's shared `fetch` wrapper throws on any non-2xx, so an
70+
anonymous `auth.me()` **rejects** rather than resolving outside its own type.
71+
Ruled by #17238: the producer moved and `SessionResponseSchema` is untouched.
72+
- `SessionUser.image`, then declared `z.string().optional()` against a route
73+
that serves `null` (#17235). It is now declared `z.string().nullish()`, so
74+
the `"image": null` every `/auth/*` session body carries parses.
75+
- the sibling `auth.login` / `auth.register`, which then normalized into `data`
76+
but set no `success` (#17234). They now run this entry's own lift, which
77+
fills `success` as well as `data`.

‎content/docs/protocol/kernel/plugin-spec.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export default definePlugin({
6868

6969
// PEER DEPENDENCIES (consumer must install)
7070
peerDependencies: {
71-
'@objectstack/ui': '^2.0.0',
71+
'@vendor/ui': '^2.0.0',
7272
},
7373

7474
// METADATA REGISTRATION
@@ -459,7 +459,7 @@ Plugin expects **consumer** to install these (not bundled).
459459

460460
```typescript
461461
peerDependencies: {
462-
'@objectstack/ui': '^2.0.0',
462+
'@vendor/ui': '^2.0.0',
463463
}
464464
```
465465

‎content/docs/references/ui/component.mdx‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ View filter rule
640640
| **staticData** | `any[]` | optional | Inline records — read SECOND by `getDataConfig`, wrapped into a `{ provider: 'value' }` config |
641641
| **filter** | `{ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Base query filter — the ViewFilterRule array form `[{ field, operator, value }, ...]`, the one filter orthography every `filter` door in this map shares; lowered to the wire `$filter`. The MongoDB-style record form is refused — see migration `element-data-source-and-object-block-filter-rule-array` |
642642
| **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | Marker order for the fetched records — the SortItem array form `[{ field, order }, ...]`, the one sort orthography every declared `sort` door on this platform shares; lowered to the wire `$orderby`. The legacy string clause (`name desc`) is refused — see migration `object-block-sort-item-array` |
643-
| **map** | `any` | optional | Map field config, the author face: `{ latitudeField, longitudeField, locationField?, titleField?, descriptionField?, zoom?, center?, style? }`. Taken WHOLE when present — the flat top-level spelling beside it is ignored |
643+
| **map** | `{ latitudeField?: string; longitudeField?: string; locationField?: string; titleField?: string; … }` | optional | Map field config, the author face — the same block `ListViewSchema.map` declares, and the one the renderer validates this node against. Taken WHOLE when present: the flat top-level spelling beside it is ignored |
644644
| **mapStyle** | `string` | optional | MapLibre style URL or spec, overriding the public demo tiles. Read before `map.style`; NOT the base node `style`, which is an inline CSS record |
645645
| **navigation** | `any` | optional | Marker-click navigation config (`{ mode: page \| drawer \| modal \| split \| popover \| none }`) |
646646
| **enableClustering** | `boolean` | optional | Group nearby markers into clusters. Absent, the renderer clusters only above 100 markers |
@@ -694,6 +694,19 @@ Sort field and direction pair
694694
| **field** | `string` | ✅ | Field name to sort by |
695695
| **order** | `Enum<'asc' \| 'desc'>` | ✅ | Sort direction |
696696

697+
### Nested Shape: `ObjectMapProps.map`
698+
699+
| Property | Type | Required | Description |
700+
| :--- | :--- | :--- | :--- |
701+
| **latitudeField** | `string` | optional | Field providing the marker latitude (used with longitudeField) |
702+
| **longitudeField** | `string` | optional | Field providing the marker longitude (used with latitudeField) |
703+
| **locationField** | `string` | optional | Field providing a combined location — a "lat,lng" string or a `{ lat, lng }` object — as the alternative to the latitudeField/longitudeField pair |
704+
| **titleField** | `string` | optional | Field displayed as the marker title (popup heading, mobile record card, and what the map search box matches on) |
705+
| **descriptionField** | `string` | optional | Field displayed as the marker description |
706+
| **zoom** | `number` | optional | Initial zoom level (1-20). Omit to let the renderer fit the camera to the queried records |
707+
| **center** | `[number, number]` | optional | Initial camera center as [latitude, longitude]. Omit to let the renderer fit the camera to the queried records |
708+
| **style** | `string` | optional | Map style URL — the MapLibre style document the renderer loads in place of the public demo tiles. The component-level `mapStyle` is read FIRST and wins when both are present; this is NOT the inline CSS `style` record a component node carries |
709+
697710

698711
---
699712

‎content/docs/references/ui/view.mdx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,7 @@ Map view configuration
775775
| **descriptionField** | `string` | optional | Field displayed as the marker description |
776776
| **zoom** | `number` | optional | Initial zoom level (1-20). Omit to let the renderer fit the camera to the queried records |
777777
| **center** | `[number, number]` | optional | Initial camera center as [latitude, longitude]. Omit to let the renderer fit the camera to the queried records |
778+
| **style** | `string` | optional | Map style URL — the MapLibre style document the renderer loads in place of the public demo tiles. The component-level `mapStyle` is read FIRST and wins when both are present; this is NOT the inline CSS `style` record a component node carries |
778779

779780

780781
---
@@ -1023,6 +1024,7 @@ View filter rule
10231024
| **descriptionField** | `string` | optional | Field displayed as the marker description |
10241025
| **zoom** | `number` | optional | Initial zoom level (1-20). Omit to let the renderer fit the camera to the queried records |
10251026
| **center** | `[number, number]` | optional | Initial camera center as [latitude, longitude]. Omit to let the renderer fit the camera to the queried records |
1027+
| **style** | `string` | optional | Map style URL — the MapLibre style document the renderer loads in place of the public demo tiles. The component-level `mapStyle` is read FIRST and wins when both are present; this is NOT the inline CSS `style` record a component node carries |
10261028

10271029
### Nested Shape: `ListView.tree`
10281030

@@ -1421,6 +1423,7 @@ View filter rule
14211423
| **descriptionField** | `string` | optional | Field displayed as the marker description |
14221424
| **zoom** | `number` | optional | Initial zoom level (1-20). Omit to let the renderer fit the camera to the queried records |
14231425
| **center** | `[number, number]` | optional | Initial camera center as [latitude, longitude]. Omit to let the renderer fit the camera to the queried records |
1426+
| **style** | `string` | optional | Map style URL — the MapLibre style document the renderer loads in place of the public demo tiles. The component-level `mapStyle` is read FIRST and wins when both are present; this is NOT the inline CSS `style` record a component node carries |
14241427

14251428
### Nested Shape: `ObjectListView.tree`
14261429

‎packages/spec/authorable-surface/ui.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,7 @@
620620
"ui/ListMapConfig:latitudeField",
621621
"ui/ListMapConfig:locationField",
622622
"ui/ListMapConfig:longitudeField",
623+
"ui/ListMapConfig:style",
623624
"ui/ListMapConfig:titleField",
624625
"ui/ListMapConfig:zoom",
625626
"ui/ListView:addRecord",

0 commit comments

Comments
 (0)