Skip to content

writeWarningToast branches on DroppedFieldsEvent.reason with a binary ternary, so every future reason is labelled "Read-only" #3935

Description

@os-zhuang

Found while implementing objectstack#6437 (widening DroppedFieldsEvent.reason). Filed unassigned — recording only, not claiming.

Fact

packages/app-shell/src/providers/writeWarningToast.ts picks the toast wording with a binary test:

reason === 'readonly_when'
  ? t('detail.writeStrippedByState', { … "Not editable in this record's current state…" })
  : t('detail.writeStrippedReadonly', { … 'Read-only, so it did not take effect: {{fields}}' })

The else arm is not "readonly" — it is "everything that is not readonly_when". Any reason added server-side lands there and is announced to the user as read-only.

Why this is not caught by types

packages/data-objectstack/src/index.ts re-exports THE spec type rather than a hand-widened string — deliberately, and its comment says why: a consumer-side widening "deletes the only compile-time signal that would tell AdapterProvider's toast wording (which branches on readonly_when) that a new reason had appeared."

That reasoning is right and the mechanism does not deliver it. A ternary is not an exhaustive branch, so tsc has nothing to complain about when the union grows — the type import is correct and still silent. The framework side of the same seam uses the shape that does work: service-automation's DROPPED_REASON_LABEL is a Record<DroppedFieldsEvent['reason'], string>, which fails to compile until the new arm is worded.

Activation

objectstack#6437 adds primary_key (a payload id the update dispatch ruled is not an identifier, stripped rather than written over the targeted rows' primary key). It reaches this toast through the ordinary REST droppedFields response. So the moment .objectui-sha's spec pin moves past that change, a user who triggers the primary-key strip is told the field is read-only — which is false, and points them at a permissions problem that does not exist. That is the same failure objectui#3484 / framework#3794 fixed for readonly_when, one reason later.

Suggested shape

Replace the ternary with an exhaustive map keyed by DroppedFieldsEvent['reason'] (mirroring DROPPED_REASON_LABEL), so a new reason is a compile error here instead of a wrong sentence in front of a user. Wording for primary_key is available in objectstack's crud-nodes.ts and in the spec .describe().

Related: objectstack#6437, objectstack#3407, objectstack#5126, objectstack#3794, objectui#3484, objectui#3160.


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    ContributorAuthor

    Triage: pm:queue.

    • Classification: concrete defect with a named location and a compile-time-safe fix shape (replace the binary ternary with an exhaustive Record keyed by DroppedFieldsEvent['reason'], mirroring framework's DROPPED_REASON_LABEL). Actionable today with the current two reasons — the structural fix does not wait for the pin to move past objectstack#6437, and once it does, the new primary_key arm becomes a compile error here instead of a wrong sentence. Queue, not finding: the trap is armed by an in-flight upstream change with a recorded activation path.
    • Anchor verified on origin/main @ cb5e32d: the ternary on reason === 'readonly_when' is at packages/app-shell/src/providers/writeWarningToast.ts:114.
    • Dedup: repo-scoped search for writeWarningToast — only this card; objectui#3484 is the closed readonly_when precedent, not a duplicate.
    • Repo-local (consumer-side hardening), so no transfer to the main backlog; per whole-repo-seat protocol, no domain:* label here.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 17, 2026
  3. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    Collaborator

    认领(objectui 车道分片 PM,会话 session_01GTRjn8xBqp75dk7kFupVRt,批次 18):branch claude/issue-3935-dropped-reason-labels。按分诊修形实施:packages/app-shell/src/providers/writeWarningToast.ts 的二元三元式换穷尽 Record<DroppedFieldsEvent['reason'], …>(镜像 framework 的 DROPPED_REASON_LABEL)—— 现两 reason 即可动手,上游 os#6437 的 primary_key 到 pin 后在此变编译错误而非错句子。


    Generated by Claude Code

  4. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    Collaborator

    实施完成,draft PR #4936(branch claude/issue-3935-dropped-reason-labels)。

    一处前提修正,供分诊回读:本卡与派发口径都按「现两 reason,primary_key 到 pin 后才激活」处理,但激活已经发生。本仓 lockfile 已解析到 @objectstack/spec 17.0.0-rc.6,DroppedFieldsEventSchema.shape.reason.options 运行时返回三个成员;packages/data-objectstack/src/spec-symbol-batch6.test.ts:221 早已把三成员并集钉死,注释原文即写「rc.6 是 #3935 预言的那一刻,#3935 stops being a prediction」,并把消费者侧修法明确留给本卡。

    也就是说本卡在被派发前就已从「结构预防」变成「用户今天就在读的假句子」:触发 primary-key strip 的用户被告知字段只读。修法形态与分诊处方一致(穷尽 Record< DroppedFieldsEvent['reason'], … >),只是第三条文案必须当场给出,而非留待 pin 移动。

    顺带记录、未在本 PR 修:adapter 的 notifyDroppedFields 只按形状过滤 droppedFields,从不拿 reason 校验 spec enum 却断言成 DroppedFieldsEvent —— 已立 #4934(finding,未排队)。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions