Skip to content

finding(timeline): dedup ObjectTimeline's useSafeObjectLabel onto the shared useSafeFieldLabel - #5781

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5623-timeline-shared-safe-field-label
Aug 23, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-5623-timeline-shared-safe-field-label

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5623

What

ObjectTimeline.tsx hand-rolled its own useSafeObjectLabel wrapper: a
docstring near-verbatim from the shared hook, a 3-member OBJECT_LABEL_FALLBACK
cast as any, and useSafeObjectLabel() { return useObjectLabel() ?? OBJECT_LABEL_FALLBACK; }.
Its single consumer (const { fieldOptionLabel } = useSafeObjectLabel();) is
replaced with the shared useSafeFieldLabel from @object-ui/react
(re-exported from @object-ui/i18n), and the local docstring/const/function
(21 lines) are deleted.

  • packages/plugin-timeline/src/ObjectTimeline.tsx — swap the import
    (useObjectLabel → useSafeFieldLabel), delete the local wrapper, update
    the call site.
  • packages/plugin-timeline/src/ObjectTimeline.test.tsx — its @object-ui/react
    mock replaces the whole module (no ...actual spread), so it needed
    useSafeFieldLabel added in place of the now-unused useObjectLabel entry
    or the destructure would throw on undefined.
  • New test:
    packages/plugin-timeline/src/__tests__/timeline-shared-safe-field-label-5623.test.tsx —
    pins the triage-named acceptance check directly against the REAL
    @object-ui/react / @object-ui/i18n exports (no useSafeFieldLabel mock):
    the timeline's one call site (fieldOptionLabel, used to resolve the
    status/priority meta-chip labels) resolves to the same raw metadata
    label with no I18nProvider mounted (unbound) and with one mounted but
    carrying no matching translation (bound) — and a third case confirms a real
    translation resolves when one is bound, so the equivalence isn't just
    because the resolver is inert.

Why this is safe (the as any cast erased a real disagreement)

The local fallback was 3 members (fieldOptionLabel, translateOptions,
fieldLabel); the shared SAFE_FIELD_LABEL_FALLBACK in
packages/i18n/src/useObjectLabel.ts is 5 (adds sectionLabel,
actionLabel). Per PR #5585, useObjectLabel() never actually returns
nullish any more (its memo holds on both the bound and no-instance paths), so
in both the old and new code the ?? FALLBACK branch is dead in practice —
which is exactly why swapping the fallback SHAPE underneath it is safe for
this one call site. Confirmed directly (see Testing) rather than asserted.

Testing

Reverse-verification, run while writing this: restored ObjectTimeline.tsx
to its pre-fix state (git checkout HEAD~1 -- packages/plugin-timeline/src/ObjectTimeline.tsx,
i.e. the local useSafeObjectLabel/OBJECT_LABEL_FALLBACK) with the NEW test
file in place, and re-ran it — same 3/3 pass, which is the direct proof of
the equivalence claim (not merely "didn't regress"): the call site behaves
identically whether reached through the local 3-member fallback or the shared
5-member one. Then restored the fix by checking out
claude/issue-5623-timeline-shared-safe-field-label's version of the file
(git status clean afterward).

pnpm exec vitest run packages/plugin-timeline/ --maxWorkers=2
 Test Files  9 passed (9)
      Tests  68 passed (68)

pnpm --filter @object-ui/plugin-timeline type-check   # tsc --noEmit && tsc -p tsconfig.test.json
EXIT=0

pnpm --filter @object-ui/plugin-timeline build        # vite build
EXIT=0

Targeted eslint --no-inline-config on the touched files: 0 errors, 41
warnings — all pre-existing (@typescript-eslint/no-explicit-any on
long-standing any props typing, react-hooks/exhaustive-deps), confirmed
by linting the pre-fix ObjectTimeline.tsx for comparison: 32 warnings there
vs 31 on the fixed file — one FEWER, the as any cast this PR removes.

Dependency closure built first (pnpm --filter '@object-ui/plugin-timeline^...' build),
exit 0.

node scripts/check-changeset-presence.mjs / check-changeset-no-major.mjs /
check-control-bytes.mjs: all pass; changeset has empty frontmatter per
.changeset/README.md's own "DON'T create a changeset for internal
refactoring with no API changes" — the removed symbols
(useSafeObjectLabel, OBJECT_LABEL_FALLBACK) were never exported.

Neither useSafeObjectLabel nor OBJECT_LABEL_FALLBACK had any other
reference anywhere in the repo (grepped packages/).

Scope

Per triage (2026-08-22) and the claim comment: mechanical dedup only. #5625
(the pivot-table ref workaround) shares the #5587 lineage but has its own
behaviour surface and stays a separate card, out of scope here.


Generated by Claude Code

claude added 2 commits August 23, 2026 08:11
.changeset/timeline-shared-safe-field-label-5623.md — empty frontmatter,
internal refactor with no exported-symbol or behavior change.
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3918.4 KB 3990.2 KB
Main entry chunk (gzip) 152.5 KB 350 KB
Entry file index-BC3PvJsL.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 10.04KB 3.72KB
app-shell (runtime-config.js) 12.80KB 4.47KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 33.99KB 8.57KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 503.77KB 113.91KB
core (index.js) 4.92KB 1.97KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 164.55KB 45.67KB
fields (index.js) 238.40KB 59.89KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 23.13KB 7.63KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 33.40KB 8.71KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.95KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.55KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.53KB 3.38KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.64KB 1.50KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 1.93KB 0.88KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.65KB 18.32KB
plugin-chatbot (index.js) 181.41KB 43.22KB
plugin-dashboard (index.js) 128.41KB 32.95KB
plugin-designer (index.js) 212.30KB 42.80KB
plugin-detail (index.js) 242.34KB 60.98KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 125.63KB 30.64KB
plugin-gantt (index.js) 164.10KB 39.87KB
plugin-grid (index.js) 200.79KB 54.26KB
plugin-kanban (index.js) 52.93KB 14.60KB
plugin-list (index.js) 111.80KB 27.20KB
plugin-map (index.js) 20.06KB 6.62KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.49KB 11.93KB
plugin-timeline (index.js) 26.49KB 7.59KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.61KB 20.74KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 44.39KB 14.99KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.33KB 0.69KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (index.js) 4.77KB 2.16KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 12.13KB 3.65KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 6.92KB 2.40KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.88KB 1.85KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 3.40KB 1.68KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-zhuang
os-zhuang marked this pull request as ready for review August 23, 2026 08:35
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 23, 2026
Merged via the queue into main with commit c6f235f Aug 23, 2026
23 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5623-timeline-shared-safe-field-label branch August 23, 2026 08:35
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
…dead issue numbers (objectui#10701) (objectstack-ai#10707)

Fixes objectstack-ai#10701
Clause-②: no (wording only; no accept set, type or behaviour moves)

Dispatched implementation of the `domain:ui` seat 1 claim (comment
`5838463073`) on objectui#10701, session
`https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC`. Wording only:
no accepted value, type or runtime behaviour changes.

## Premise, re-measured first

REST `GET /repos/objectstack-ai/objectui/issues/{n}`, 2026-09-25:

| number | objectui | same number in objectstack |
|---|---|---|
| 5740 | 404 | 200, an unrelated service-analytics PR |
| 5784 | 404 | 200, an unrelated stub-driver rename issue |
| 5782 | 404 | 200, an unrelated metadata-protocol PR |
| 5806 | 404 | 200, an unrelated driver-sql integration-test PR |
| controls 5736, 5742, 5778, 5790 | 200 | |

None of the four answers 200 in objectui, so the premise holds. The
sister-repo numbers resolve, but to unrelated subjects, so a
repo-qualified citation would send a reader to the wrong record. This PR
cites the landing commits instead. Both are ancestors of `main` (`git
merge-base --is-ancestor`, exit 0 for each):

- `b55a34647` feat(types): retire 'agenda' from CalendarViewMode and the
zod enum
- `ed8df3e50` feat(types): retire 'agenda' from defaultView on all three
declaration faces

## Citation form, site by site

- **The three zod `.describe()` strings** (`CalendarViewSchema.view`,
the list-view `calendar` config's `defaultView`,
`ObjectCalendarSchema.defaultView`): the pointer is dropped, and they
now end `('agenda' was retired)`. That sentence stands alone. These
strings ship in `dist` and reach whatever reads the zod `description` at
runtime, a reader who has no repository to resolve a bare sha against.
No `.describe()` string under `packages/types/src/zod/` carries a commit
citation today, so there was no runtime-string precedent to follow.
- **Docblocks and the zod line comment**: they cite the landing commit
as a backticked 9-character sha. That is the form published docblocks,
changesets and docs already use, for example "landed as `89d7b35a7`" in
`packages/data-objectstack/src/metadata-client.ts` and "deleted from the
repository in `8c5d20455`" in
`.changeset/6342-dead-migration-guide-pointers.md`.
- **`schema-reference.md`, the `CalendarViewMode` row**: "retired in
`b55a34647` and now fails validation".
- **The two retirement changesets**: the pointer to themselves is
dropped. This repo's `@changesets/cli/changelog` is
`@changesets/changelog-git`, which prefixes each CHANGELOG entry with
the short hash of the commit that ADDED the file (`git log
--diff-filter=A`). That is the landing commit, so the released entry
already carries `b55a346` / `ed8df3e`. Where the `defaultView` changeset
points at the `CalendarViewMode` retirement, it now cites `b55a34647`.
- **The i18n batch changeset** (`4730-retire-dead-locale-key-batch.md`):
"labelled a view mode `b55a34647` retired from `CalendarViewMode`".
- **Pending changeset frontmatter is byte-identical.** The sha256 of
each frontmatter block is equal before and after, and
`check-changeset-overwrite` prints `declared at base` equal to `declares
now` for all three. The filenames keep their `5740` / `5784` suffixes: a
filename is not a citation, and a rename would read as deleting one
declaration and adding another.

## Which sites actually publish: the card's two classes, measured
against `dist`

I built `@object-ui/types` and `@object-ui/plugin-calendar` and grepped
`dist`:

- **Card's "comment-only" sites that do ship.** The `complex.zod.ts`
docblocks on `CalendarViewModeSchema` and `CalendarViewSchema` ship in
`dist/zod/complex.zod.d.ts` and `.js`. The `objectql.zod.ts` line
comment above the calendar config's `defaultView` ships in
`dist/zod/objectql.zod.js`, because tsc emits with `removeComments:
false`. This is published text, so this PR repairs it along with the
rest. All three are the optional adjacent comments the claim's file
surface already lists.
- **Card's "published JSDoc" site that does not ship.** Neither the old
nor the new pointer in `plugin-calendar/src/index.tsx` appears in
`packages/plugin-calendar/dist`. The same docblock's `objectui#8201` is
missing too, while a code string from the same file (the `defaultView`
input description) is present as the control. The site is repaired
anyway because the triage names it, and it is declared comment-only.
- **Result.** After this PR, `@object-ui/types` `dist` contains no
`objectui#5740` or `objectui#5784`.

## Census

`git grep -nE 'objectui#5740|objectui#5784|objectstack-ai#5740|objectstack-ai#5784|objectstack-ai#5782|objectstack-ai#5806'`
over `packages/`, `content/` and `.changeset/` at the base finds 26
lines, all in files the card names. Outside those three roots it finds 0
(control: `objectstack-ai#7853` hits 5 files). After this PR there are 0 non-test
hits. The 10 lines that remain are docblocks and comments in four test
files. Those do not ship and are optional per the triage (see Acceptance
notes).

## Changeset

`.changeset/10701-agenda-retirement-citations.md` declares
`@object-ui/types: patch` for the `.describe()` wording (precedent:
`10084-grid-ceiling-describe.md`). The `plugin-calendar` edit is
comment-only, as measured above. This repo's precedent for comment-only
`src/` edits is an empty declaration (for example
`10159-action-preview-reference-citation.md`). Instead of adding a
second file, the new changeset's body says `plugin-calendar` declares no
release. `check-changeset-presence` requires one added declaration, and
this one satisfies it.

## Pins

`grep -rln "agenda' was retired" packages/` returns only the two zod
source files. No test snapshots or reads the old `describe` text, so no
pin changed. No ablation was run, because nothing executable changes.

## Verification, all on `045a05c26` (the patch round's re-runs at
`f730dc423` are under Acceptance notes)

- `pnpm exec vitest run packages/types/src/__tests__/`: `Test Files 240
passed (240)`, `Tests 5276 passed (5276)`.
- `pnpm exec vitest run packages/plugin-calendar/`: `Test Files 46
passed (46)`, `Tests 328 passed (328)`.
- Suites that read the changed markdown as data, taken from `node
scripts/markdown-test-inputs.mjs --list` (classes `.changeset/**`,
`content/docs/**` and `content/docs/api/schema-reference.md`): the 15
readers under `scripts/__tests__/` give `Test Files 15 passed (15)`,
`Tests 682 passed (682)`. The readers under `packages/types` ran in the
240 above.
- `type-check`: `@object-ui/types` (tsc on the main, examples and test
configs) and `@object-ui/plugin-calendar` (after building its dependency
closure). Both exit 0.
- Doc gates: `pnpm docs:check-links` prints "Links are valid across 17
scan roots."; `pnpm check:doc-types` prints "Every documented component
type is registered."; `pnpm check:doc-fences`, `check-doc-example-ids`,
`check-doc-expression-carriage` and `pnpm check:doc-example-readers`
exit 0.
- `pnpm check:control-bytes` is OK. `pnpm check:new-line-citations`
prints `VERDICT new-cross-file-line-citations: 0 new citation(s)`. `node
scripts/check-changeset-presence.mjs` prints "5 source file(s) of 2
released package(s) changed, and this change declares 1 changeset(s)".
`pnpm changeset:check` exits 0. `pnpm check:pending-changeset-literals`
prints "No test source names a pending changeset."
- `pnpm check:changeset-claims` (report-only): 50 pending changesets
name a file this PR touches (the shared `complex.ts` / `objectql.ts` /
zod files and `schema-reference.md`). I read them against the diff. No
pending changeset quotes the changed text, and the two retirement
changesets' closing sentences about the `describe` strings stay true.
- `check-changeset-overwrite` (report-only): 3 files modified, each its
case 2 (a deliberate prose correction), with declarations unchanged.
- eslint (`--no-inline-config --format json`) over the five touched
source files: 5 files, 0 errors, 49 pre-existing warnings, none on an
edited line. `eslint.config.js` enables no type-aware linting, so a
comment or string diff cannot change another file's result.
- NOT MEASURED: `check-doc-snippet-types` and `check-doc-example-types`.
Reason: both exit 2 with PREREQUISITE NOT MET (they need a 34-package
scoped build). They type-check code fences, and the only doc edit here
is a table cell.

## Acceptance notes

- **`objectui#5783`, folded in the PM patch round (head `f730dc423`).**
It also answers 404 (control: `objectstack-ai#5781` answers 200), and it was in a
changeset this PR already edits, so the seat extended the claim's
surface to that one pointer.
`.changeset/4730-retire-dead-locale-key-batch.md` line 7 now reads ``
`calendar.agenda` included). `` with no replacement citation, as a
single hunk `@@ -7 +7 @@`. The frontmatter sha256 is unchanged
(`57e32eec…`). The patch round re-ran control bytes, changeset presence
/ `changeset:check` / claims / overwrite / pending literals, and the two
`scripts/__tests__` suites that read `.changeset/**` (2 files / 44
tests): all exit 0. The one residual is the header docblock of
`dead-key-batch-retired-4730.test.ts`, which does not ship and sits with
the other comment-only test sites below.
- The docblocks and comments in
`calendar-view-mode-agenda-retired.test.ts`,
`default-view-agenda-retired.test.ts`,
`dead-key-batch-retired-4730.test.ts` and
`scalarKeysAreDeclaredAndHonoured-8201.test.ts` still cite the dead
numbers. They do not ship and are optional per the triage.
- The card's published/comment-only split was wrong in both directions
(see above). A `dist` grep settles that classification for future cards
of this class.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(timeline): ObjectTimeline hand-rolls a third useSafeFieldLabel, with a 3-member fallback where the shared one has 5

2 participants