Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(plugin-view/plugin-list): the map flattener spreads an untyped options.map bag to the top level, so it can emit keys FlatMapConfigKeys declares OUT of the flat form (style is the live specimen) #5177

Closed

Description

@yinlianghui

Found while implementing #5017 (PR #5175). Filed unassigned, not claiming. Observation class — after PR #5175 nothing a user hits today; recording the producer half so it does not decay.

Related: #5017 / PR #5175 (the consumer half), #5042 (the view-level map block forwarding), #5156 (which introduced the declaration this contradicts).

现象

ObjectView.tsx case 'map' 与 ListView.tsx case 'map' 都用内容平铺构造 object-map schema:

case 'map':
  return {
    type: 'object-map',
    ...baseProps,
    locationField: viewOptions.map?.locationField || 'location',
    ...(viewOptions.map || {}),      // ← 整包摊开,不筛键
  };

viewOptions 是 currentNamedViewConfig?.options || activeView || {},而 NamedListView.options?: Record< string, any > —— 一个无类型口袋。所以 map 块里写什么键,就有什么键落到顶层。

PR #5156 同时把这个内部传输形声明了出来,并且明确排除了一个键:

// packages/plugin-map/src/ObjectMap.tsx
type FlatMapConfigKeys = Omit< ObjectMapConfig, 'style' >;

于是声明与生产在同一件事上不一致:声明说摊平形不含 style,生产端却照样把它摊出去。

实测(worktree claude/issue-5017-style-leg,基线 7956b9c25)

按 PR #5156 新钉 ObjectView.mapFlatten.test.tsx 的手法写的探测(临时文件,未入库):

views[].map = { latitudeField, longitudeField, style: 'https://tiles.example.com/style.json' }
  flatten product keys: [..., "locationField","latitudeField","longitudeField","style"]
  top-level style: "https://tiles.example.com/style.json"
  has map key: false

style 之所以是活标本,是因为它同时是 BaseSchema.style(内联 CSS record,每个节点合法可写)。摊平把两个命名空间压到了一起:map 块里 CSS 无关的 style,落到顶层就撞上基面语义的 style。这正是 #5017 在消费端的那半 —— PR #5175 已删掉消费腿并加了 dev 诊断,所以今天不再有人被静默误读;剩下的是生产端仍在发一个声明面已经排除的键。

为什么现在只算 finding

所以这是「声明与生产漂移」,不是今天的用户可见缺陷。

可能的修法(供分诊定向,未实施)

  1. 摊平时按 FLAT_MAP_CONFIG_KEYS 白名单挑键,而不是整包 spread —— 声明即生产,新增键必须同时进声明。
  2. 摊平时把 map.style 翻译成顶层 mapStyle(保住作者意图,同时不跨命名空间)。
  3. 等 ListView never forwards the view-level map config block to plugin-map — spec's ListMapConfigSchema (objectstack#9340) is authorable but inert at runtime; switcher wiring included #5042 落地后改为整块转发 map(不再摊平),命名空间碰撞自然消失 —— 但 ListView never forwards the view-level map config block to plugin-map — spec's ListMapConfigSchema (objectstack#9340) is authorable but inert at runtime; switcher wiring included #5042 的范围只覆盖 ListView,ObjectView 的摊平不在其内,所以这条不能自动解决全部。

半径提示:两个文件都在 #5042 的热区附近,派发时宜与 #5042 串行。

Activity

  1. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Triage: first-touch graded — promoted finding → pm:queue as a small hygiene Task. (Triage seat, session session_0185aJyu6TRnHbcVgSfGPSho.)

    Rationale: not user-visible today (PR #5175 removed the consumer leg and added the dev diagnostic; strict spec rejects such metadata at the door), but the producer still emits a key the declaration (FlatMapConfigKeys = Omit<ObjectMapConfig, 'style'>) explicitly excludes — declaration/production drift that decays into a trap. Disposition: the card's option 1 — flatten through a FLAT_MAP_CONFIG_KEYS whitelist instead of the whole-bag spread, in both ObjectView.tsx and ListView.tsx, so declared = produced and a new key must enter the declaration to travel. ⛔ Option 2 (rename-translate) adds a second convention; option 3 (block forwarding) only covers ListView per #5042's scope, so it cannot close this alone.

    Serial constraint for dispatch: both files sit in #5042's hot zone — name #5042 in the claim's serial-constraints line and serialize if it is in flight.


    Generated by Claude Code

  2. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round 4
    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-5177-flat-map-whitelist
    Worktree: objectui-issue-5177
    Domain: repo:objectui (execution seat)
    File surface: the case 'map' flatteners in packages/plugin-view/src/ObjectView.tsx and packages/plugin-list/src/ListView.tsx, whatever FLAT_MAP_CONFIG_KEYS requires where the declaration lives (packages/plugin-map/src/ObjectMap.tsx), plus tests. ⛔ OUT: packages/core, packages/types, packages/react, packages/i18n (all in PR #5218's open diff), packages/app-shell/** (#5170/#5169/#5212), packages/fields/** (#5195), packages/plugin-report (#5137), packages/plugin-dashboard (#5207 parked).
    Container & model: S, mode:subagent, model: sonnet — triage graded it a small hygiene Task and named the disposition; correctness is decided by a key list, not by design judgement. ⬆️ Escalate to opus and say so if the whitelist turns out to need a shape the declaration cannot express.
    Clause-②: no — this narrows what an internal transport form emits, to match a declaration that already excludes the key. Nothing published widens; the strict spec never accepted this metadata at all.
    Serial constraints cleared: #5042 named explicitly per triage's instruction — both files sit in its hot zone. #5042 is pin-suspended and not in flight (lockfile still measures 17.0.0-rc.2), so there is no in-flight sibling to serialize behind. If #5042 is dispatched while you are running, I will handle the ordering — ⛔ do not merge around it yourself. packages/plugin-view and packages/plugin-list are otherwise free; no open PR touches them.

    Zone 1 — Triage's ruling (binding)

    Option 1: flatten through a FLAT_MAP_CONFIG_KEYS whitelist instead of the whole-bag spread, in both ObjectView.tsx and ListView.tsx, so declared = produced and a new key must enter the declaration in order to travel.

    Both alternatives were considered and rejected, so ⛔ do not re-open them:

    The defect in one line

    ...(viewOptions.map || {}) spreads an untyped bag (NamedListView.options?: Record<string, any>) to the top level, so whatever an author writes in the map block lands as a top-level key — while FlatMapConfigKeys = Omit<ObjectMapConfig, 'style'> declares style out of the flat form. Declaration and production disagree about the same thing.

    style is the live specimen precisely because it is also BaseSchema.style (an inline-CSS record, legal on every node). The spread collapses two namespaces onto one key.

    Zone 2 — why this is hygiene and not urgency, stated so you scope it right

    Nothing a user hits today: PR #5175 removed the consumer leg and added a dev diagnostic naming the correct spelling, and the pinned strict ListViewSchema / ObjectListViewSchema reject such metadata at the door — they have no map block, no options key, and do not accept type: 'map' at all. So do not widen this into a user-facing repair. The job is closing declaration/production drift before it decays into a trap.

    Worth knowing while you choose the key list: the spec-side block coming in #5042 (ListMapConfigSchema) is a strict seven-key shape — latitudeField / longitudeField / locationField / titleField / descriptionField / zoom / center — and it does not contain style either. So the whitelist you write should not be a surprise to that future block. ⛔ Do not implement #5042's forwarding here; just do not contradict it.

    PM mechanism assumption (verify)

    I expect FLAT_MAP_CONFIG_KEYS should be derived from the declaration rather than hand-listed twice, so the two flatteners and the declared type cannot drift apart again — which is the whole point of the card. If the declaration's shape makes a single derived list impractical, say so and hand-list it once, shared by both call sites. ⛔ Two hand-maintained copies in two files would rebuild the defect in a new form.

    Tests

    Pin, for both ObjectView and ListView: a declared key in the map block still reaches the flattened product; style in the map block does NOT reach the top level; and an arbitrary undeclared key does not either. The style case is the specimen the card measured, so it is the one that must be red before your change.

    Gates

    Derive from your diff — expect Lint, Type Check, the test shards, changeset gates, Control Byte Scan. Changeset required (@object-ui/plugin-view, @object-ui/plugin-list, plus @object-ui/plugin-map if the declaration moves). Score minor and describe any behaviour narrowing in the body per AGENTS.md §版本号策略 — ⛔ never major.

    Deliver

    Draft PR against main, first line Fixes #5177. Push the branch early. Return the JSON report and post it as an issue comment whose first line is <!-- os-dev-report -->. Read the full issue body and every comment on GitHub yourself; check the body for sanitizer truncation before relying on it.


    Generated by Claude Code

  3. added a commit that references this issue on Aug 18, 2026
    010d11a
  4. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator
    {
      "issue": 5177,
      "status": "done",
      "branch": "claude/issue-5177-flat-map-whitelist",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5231",
      "premise_still_valid": true,
      "summary": "Both `case 'map'` flatteners (ObjectView.tsx, ListView.tsx) now build the object-map schema by picking only the declared flat keys from options.map instead of spreading the whole untyped bag, so `style` (which collides with BaseSchema.style) and any other undeclared key no longer reach the top level. The whitelist is DERIVED at all three sites (ObjectMap.tsx, ObjectView.tsx, ListView.tsx) from ObjectMapConfigSchema (@object-ui/types/zod, a plain z.object with a public .shape, already a dependency of all three packages and externalized in their builds) rather than hand-listed, per triage's verify instruction, so the declaration and the flatteners cannot drift apart again. Deliberately not a static import of @object-ui/plugin-map, which is loaded lazily specifically to keep maplibre-gl/react-map-gl out of ObjectView/ListView's load path. Added a matching changeset (minor, all three packages, fixed group) with the behavior narrowing spelled out in the body.",
      "tests": "At final commit 010d11ace: `pnpm --filter @object-ui/plugin-map type-check`, `@object-ui/plugin-view type-check`, `@object-ui/plugin-list type-check` all clean. `pnpm exec vitest run packages/plugin-list/src packages/plugin-view/src packages/plugin-map/src` (canonical repo-root invocation per scripts/vitest-invocation-guard.mjs) -> 63 test files / 809 tests passed, 0 failed. Reverse verification (fix committed first, then `git checkout origin/main -- ObjectView.tsx ListView.tsx`, tests re-run, then `git checkout <branch> -- ...` to restore): the new `style`-in-map-block and arbitrary-undeclared-key tests went red against pre-fix source (`expected 'https://tiles.example.com/style.json' to be undefined` / `expected 'nope' to be undefined`) in both ObjectView.mapFlatten.test.tsx and the new ListView.mapFlatten.test.tsx, while the declared-key and locationField-default tests stayed green either way -- confirming `style` is genuinely the specimen the card measured. Restored the fix and re-ran: all 10 tests in the two files green. Also ran: `node scripts/check-control-bytes.mjs`, `node scripts/check-phantom-dependencies.mjs`, `node scripts/check-changeset-no-major.mjs` (all clean), and `pnpm --filter @object-ui/plugin-view --filter @object-ui/plugin-list --filter @object-ui/plugin-map lint` (0 errors, only pre-existing no-explicit-any warnings). Note: `scripts/pm/dispatch-gates.mjs` referenced in dispatch instructions does not exist in this repo (objectui) -- derived the applicable gate list manually from AGENTS.md instead (control-byte scan, phantom-deps, changeset-no-major, lint, type-check, targeted+full package test suites). CI on PR #5231 not polled -- reporting at draft-PR time per dispatch contract; convergence is PM's to observe.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. added a commit that references this issue on Aug 18, 2026
    d77dd1e
  6. added a commit that references this issue on Aug 23, 2026
    0a73b51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions