Skip to content

test(console): pin the four object-metric query members (objectui#8071 slice 8) - #8955

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-8071-member-pins-slice-8
Sep 10, 2026
Merged

test(console): pin the four object-metric query members (objectui#8071 slice 8)#8955
os-warren merged 1 commit into
mainfrom
claude/issue-8071-member-pins-slice-8

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Refs #8071

Slice 8 of the per-block member-pin transition, and the first that takes a partial bite of a multi-key block — slice 7 exhausted the "close a block by taking its last key" shape.

The batch, stated as a rule

Every object-metric member that shapes the aggregate query behind the NUMBER the tile paints.

Four qualify: dataSource, aggregate, filter, compareTo. The rule is mechanical, not editorial — those four are exactly the members that reach ObjectMetricWidget's fetchMetric / computeOne: dataSource gates the effect, and aggregateKey / resolvedFilterKey / compareToKey are its other dependencies. The two left, trend and drillDown, reach neither. They shape what is drawn around the number once it exists — a static badge beside it, a drawer opened by clicking it — and neither can change the number.

⇒ What slice 9 inherits is cheap to state: the two presentation members of object-metric. They were deliberately left as a pair rather than one being swept in: drillDown does issue a query of its own (the drawer's record list, off the same resolved filter), so "reaches an adapter" would have been the wrong cut. That is recorded in the ceiling docblock so the next reader does not re-derive it.

Ledger, AST-measured on this branch's own head

TypeScript AST walk — ts.createSourceFile, VariableDeclaration, initializer unwrapped through as / satisfies / parens, direct PropertyAssignment children, boundaries from AST node spans.

before (aeaa0f64c) after (04ee89741)
MEMBER_PIN_EXEMPTIONS 37 33
MEMBER_PIN_EXEMPTION_CEILING 37 33
MEMBER_PINS 53 57
distinct block prefixes 6 6
NEWLY_JUDGED_UNPINNED_MEMBERS 2 2 (untouched)
object-metric. exemptions 6 2

The delta balances on one instrument: −4 exemptions, −4 ceiling, +4 pins. Both initializers are plain object literals with zero non-PropertyAssignment members and zero keys lacking a ., so the prefix breakdown is the whole population.

Remaining 33: object-grid. 15 · object-form. 7 · object-master-detail-form. 6 · object-kanban. 2 (held, unruled on #8913) · object-metric. 2 · record:related_list. 1 (the NO_READ_SITE_TO_PIN sentinel).

Instrument cross-check. A key-anchored regex bounded to top-level indentation inside the AST span agrees exactly on both objects: 33 = 33 and 57 = 57. The value-anchored variants still disagree, as documented: AWAITING_A_PIN reads 38 and AWAITING_A_PIN\b reads 34 against a true 30 value-uses (the exemption values decompose 30 / 1 NO_READ_SITE_TO_PIN / 2 AWAITING_A_PIN_NEWLY_JUDGED = 33). The AST is what the numbers above come from.

The four pins

Two promote pre-existing files, each read end to end and each given the one row it was missing.

  • object-metric.dataSourcepackages/plugin-dashboard/src/ObjectMetric.elementDataSource.test.tsx. It already drove the binding's object and view members through the real renderer, with an unresolvable view reporting rather than aggregating the whole object. What it lacked was the negative direction, added here: the view fixture's columns, sort and pagination are asserted out of the aggregate call. Without that, the pin would have been satisfied by a mapping that forwarded everything — which for a metric means one silently ordered and paged by a list view's presentation settings.
  • object-metric.compareTopackages/plugin-dashboard/src/__tests__/ObjectMetricWidget.compareTo.test.tsx. It already asserted the { kind, dimension? } member set on a quarter-scoped fixture where the two kind values disagree about both window and label, plus the no-compareTo single-pass control. It drove the widget directly, so this PR adds the row that mounts type: 'object-metric' through SchemaRenderer. That row is why the file names the block at all: the locator requires it, and a docblock mention alone would have been the locator satisfied by prose rather than by behaviour. Ablation B4 confirms the row is load-bearing, not decorative.

Two are new, sharing one file — packages/plugin-dashboard/src/__tests__/objectMetricQueryMembers-8071.test.tsx, the shape slice 3 used for the record-picker label/placeholder pair and slice 6 for chatter/discussion:

  • object-metric.aggregate — a three-member options bag whose members are read twice: into the call (field / function / groupBy, with groupBy optional and defaulting to one bucket, an authored value outranking it, the bag asserted whole) and back out of the response (count sums FIELD_count across every row; any other function reads the first row's FIELD_FUNCTION). Both arms run on the same two-row response, so only a readback reading both members can answer 120 on one and 7 on the other. The key's absence is a member semantic too, and the file's non-vacuity floor: with no aggregate, the query verb changes to find() and the value becomes the row count.
  • object-metric.filter — no named member set, so the member shape is the spelling, and there are two: flat under its own name inside the aggregate bag, and wrapped as an OData-style $filter on the no-aggregate() fallback. Plus placeholders resolved before the query, and the predicate read by value (a deep-equal rebuild must not re-probe; a changed comparand must, and must carry the new predicate).

The aggregate rows author no filter on purpose, so the bag comparison is blind to the filter spelling and the two ablations fail for their own reasons rather than as a pair.

Candidates read and rejected, per file

Twelve collected test files name object-metric — the locator's own precondition — and every one was read end to end.

file why not credited
public-block-binding-reach.test.tsx States its own narrowness in prose: one question per block, "did any call carry the object name". Lists aggregate only as a plausible sample value.
widget-dom-leak-sweep.test.tsx The DOM-attribute canary. Its aggregate is a stub returning []; object-metric is one render target among many.
ObjectMetricWidget.i18nLabel.test.tsx Authors aggregate, trend and drillDown purely to make the drill-down reachable; its subject is I18nLabel resolution. Crediting it for trend or drillDown would repeat slice 7's action-bodyShape-forward.test.tsx mistake in a new place.
ObjectPivot.elementDataSource.test.tsx Names object-metric once, in prose, as the sibling shape. Its subject is object-pivot.
ElementDataSourceGate.test.tsx Names object-metric once, in prose, listing which wirings share the gate.
public-contract.test.ts Membership list plus one prose mention about lazy registration.
dashboardComponents.mapParity.test.ts Membership list.
filterIsDeclaredInput-7712.test.ts One prose mention; its subject is object-calendar.
PageBlockInspector.colorLabelling.test.tsx, PageBlockInspector.i18n.test.tsx, block-config-i18n.test.ts Designer-inspector fixtures.
registry-inputs-spec-parity.test.ts The gate itself, and excluded from its own glob by construction.

Ablations

Every leg proved the mutation on disk before any result was read — anchor grep counts on injected and removed text, plus a git hash-object differing from the HEAD blob — and restored with git checkout HEAD -- ABSOLUTE_PATH from a trap … EXIT INT TERM, with restoration proved by blob hash, never by an exit code.

A0 — the gate mechanism. One deleted exemption re-added, ceiling untouched. apps/console/src/__tests__/registry-inputs-spec-parity.test.ts, head blob 8102fd30b, mutated ba6e07fea; removed anchor 1 to 0, injected anchor 0 to 1. 3 rows red (198 tests, 3 failed): the total-and-disjoint member-shape census, "carries no stale member-pin exemption", and "the member-pin exemption list only ratchets DOWN". Restored to 8102fd30b, git diff HEAD empty.

One ablation per new pin, the whole new pin set run together so neighbour independence is observed rather than asserted (19 tests across the three pin files):

leg mutation blob red
B1 aggregate groupBy: aggregate.groupBy || '_all' becomes a hard-coded '_all' a2da27d14 to b08defb10 1 — "lets an authored groupBy outrank that default". 18 green.
B2 filter the aggregate bag's flat filter becomes $filter a2da27d14 to 3063e6ff9 10 across all three files — see below.
B2b filter the find() fallback's $filter becomes flat filter a2da27d14 to 8e1527b57 1 — "reaches the find() fallback WRAPPED as $filter". 18 green.
B3 dataSource the gate stops re-binding the composed filter onto the widget 1479e50aa to ddf23632f 2 — both dataSource rows. 17 green.
B4 compareTo .kind is ignored: every comparison forced to previousPeriod a2da27d14 to a1248b0ab 3 — the two previousYear-reading rows plus the new registry row. The previousPeriod row and the no-compareTo control stay green, which is the discrimination. 16 green.

B2 is the one to read carefully, and it is reported as observed rather than smoothed. Collapsing the two wire spellings is precisely the defect this pin exists for, and its blast radius is wide: 10 of 19 rows, spanning all three pin files, because the flat filter spelling is what every metric fixture in this family reads through. What it did not touch is the finding: all six aggregate rows stayed green (they author no filter, by design), and so did reaches the find() fallback WRAPPED as $filter. B2b is the other direction of the same pair and fails narrowly — exactly one row — which is what shows the two spellings are separately pinned rather than jointly.

⚠️ One reading was taken twice and the first is reported void. B2b's first run used a shell-mangled removed-anchor pattern that read 0 -> 0; the mutation itself was still proven (injected anchor 0 to 1, blob differing), but a malformed anchor reading is not a reading, so the leg was re-run with a correctly escaped pattern. It reproduced identically — same mutated blob 8e1527b57, same single red row — and the corrected counts are 1 -> 0 and 0 -> 1.

Verification

Every exit code captured into a variable before any pipe.

  • Gate pnpm exec vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.tsexit 0, 198 passed (198). Baseline on aeaa0f64c was also 198 passed, so the count is unchanged and the population moved inside it.
  • Full suites of both touched packages pnpm exec vitest run packages/plugin-dashboard/ apps/console/exit 0, 202 files / 2068 tests passed.
  • Final confirmation on the restored tree (gate + all four pin files) — exit 0, 217 passed (217); git status --porcelain empty at 04ee89741.
  • type-check pnpm --filter @object-ui/console --filter @object-ui/plugin-dashboard type-checkexit 0. The log echoes both scripts (tsc --noEmit && tsc -p tsconfig.test.json; tsc --noEmit && tsc -b tsconfig.node.json --force), so the test files are type-checked, not merely collected. ⚠️ A first run exited 2 with a wall of TS2307 Cannot find module '@object-ui/…'; that was the unbuilt workspace closure, not this diff — it cleared after pnpm --workspace-concurrency=2 --filter '@object-ui/console^...' --filter '@object-ui/plugin-dashboard^...' build and the only errors that remained were five in the new file, which are fixed here.
  • lint pnpm --filter @object-ui/console --filter @object-ui/plugin-dashboard lintexit 0, and read for the literal word rather than trusted to the exit code: packages/plugin-dashboard lint: ✖ 451 problems (0 errors, 451 warnings) and apps/console lint: ✖ 212 problems (0 errors, 212 warnings). The log echoes Scope: 2 of 47 workspace projects and both script names, so "the filter matched no script" cannot read as a pass. The new file draws no rows at all; the rows on the two edited files are the pre-existing no-explicit-any warnings they already carried.
  • changeset node scripts/check-changeset-presence.mjsexit 0: 4 source files of 2 released packages changed, 1 changeset declared, empty frontmatter (test-only, nothing released).
  • governed surface node scripts/check-governed-queue-guard.mjs --test …exit 0, NOT GOVERNED. The PR still stays draft per dispatch; the PM lands it.
  • control bytes grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over all five touched paths — no matches.

Scope

Nothing here touches packages/plugin-kanban/**, packages/plugin-gantt/**, packages/types/src/complex.ts, zod/complex.zod.ts, packages/sdui-parser/src/kanban-quick-add.ts or the kanban/gantt docs — the live surface of #8865. object-kanban.columns / object-kanban.dataSource stay exempt on the ruling ground (#8913 unruled), and record:related_list.actions was not re-measured here because this slice took a different block: slice 7's measurement stands as the last one taken.

验收备注

  • noted, not filedObjectMetricWidget.computeOne's find() fallback returns records.length, so on a page-limited adapter the tile would paint the page size rather than the total. Not filed: no reproduction was measured, and it is a design question about the fallback rather than a defect with a named failing probe. Successor: slice 9, which takes this block's remaining two keys, and anyone editing ObjectMetricWidget.tsx.
  • noted, not filedObjectMetricWidget.i18nLabel.test.tsx mentions both of the keys slice 9 will take (trend as a props bag whose subject is the label inside it, drillDown as { enabled: true } to make the drawer reachable). It satisfies any mechanical locator for either and asserts nothing about either member set. Not filed: it is a warning for one specific future slice, not a defect. It is written into the ceiling docblock, where that slice will read it. Successor: slice 9.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w


Generated by Claude Code

…1 slice 8)

Slice 8 of objectui#8071 takes the first PARTIAL bite of a multi-key block.
The batch is a rule, not a pick: every `object-metric` member that shapes the
aggregate query behind the NUMBER the tile paints — the four that reach
`ObjectMetricWidget`'s `fetchMetric`/`computeOne` (`dataSource` gates the
effect; `aggregateKey`, `resolvedFilterKey`, `compareToKey` are its other
dependencies). `trend` and `drillDown` reach neither: they shape what is drawn
around the number once it exists, and stay exempt for the next slice.

MEMBER_PIN_EXEMPTIONS 37 -> 33, MEMBER_PIN_EXEMPTION_CEILING 37 -> 33 and
MEMBER_PINS 53 -> 57, in this one commit. NEWLY_JUDGED_UNPINNED_MEMBERS is
unchanged — no block it names was touched.

Two pins promote pre-existing files, each read end to end and each given the
one row it was missing: `ObjectMetric.elementDataSource.test.tsx` gains the
negative direction (the view's `columns`/`sort`/`pagination` asserted OUT of
the aggregate call, so the pin cannot be satisfied by a mapping that forwards
everything), and `ObjectMetricWidget.compareTo.test.tsx` gains the row that
mounts `type: 'object-metric'` through SchemaRenderer, so the claim that the
block's gate forwards the key untouched is asserted rather than assumed.
`aggregate` and `filter` share one new file, written so the aggregate rows
author no filter and stay independent of the filter spelling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3490.7 KB 3512.7 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-DaAJC-kx.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) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
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) 40.18KB 10.59KB
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) 11.08KB 4.58KB
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) 500.20KB 114.67KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 28.04KB 9.46KB
data-objectstack (index.js) 205.46KB 56.78KB
fields (index.js) 246.97KB 62.30KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
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.99KB 0.87KB
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 (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.63KB 19.99KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 132.41KB 34.84KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 253.19KB 65.62KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.96KB 40.93KB
plugin-grid (index.js) 210.86KB 57.28KB
plugin-kanban (index.js) 57.58KB 16.47KB
plugin-list (index.js) 112.54KB 27.65KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.80KB
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) 4.47KB 1.63KB
react (SchemaRenderer.js) 83.34KB 27.61KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 2.71KB 1.35KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
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 (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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-warren
os-warren marked this pull request as ready for review September 10, 2026 09:35
@os-warren
os-warren added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit dbc7e5b Sep 10, 2026
35 checks passed
@os-warren
os-warren deleted the claude/issue-8071-member-pins-slice-8 branch September 10, 2026 09:53
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.

1 participant