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.

DetailView's own record header repeats the title/action width arbitration hazard fixed in page:header #7281

Closed

Description

@hotlong

Split out of #7244 (renderer-side fix landed on page:header). Not measured in a browser — identified statically while fixing the sibling header, and filed rather than fixed to keep that PR to one verified change.

What #7244 fixed

packages/components/src/renderers/layout/containers.tsx laid the record header out as a flex-nowrap row: a shrink-0 action tail plus a flex-1 min-w-0 title column. The title column, being the only flexible item, absorbed the whole width deficit — measured at a 799px viewport, header 687px, tail 641.5px, leaving the h1 29.5px. Fixed with an sm:min-w-48 floor on the title column plus sm:flex-wrap on the header.

The same shape elsewhere

1. packages/plugin-detail/src/DetailView.tsx:919 — same shape, no protection.

div.flex.flex-col.sm:flex-row.items-start.justify-between.gap-3.sm:gap-4.pb-4.border-b
  div.flex.items-start.gap-2.sm:gap-3.flex-1.min-w-0     <- title column, no floor
    div.flex-1.min-w-0
      h1.text-xl.sm:text-2xl.font-bold.truncate

No flex-wrap on the row and no width floor on the title column — the two things whose absence produced the reported bug. This header is the one that renders when the host does NOT supply a page:header (the file's own comment: suppressed when the host page renders its own page:header), so it is a live path, not dead code.

2. packages/layout/src/PageHeader.tsx:236 — partially protected.

Its outer row is already flex flex-wrap items-center gap-x-4 gap-y-2, so the deficit has somewhere to go. But the title column is still flex flex-col min-w-0 flex-1 with no floor, and flex-1 means a flex-basis of 0 — so the column never contributes width that would force a wrap, and a wide slot sitting on the same line still takes the remainder. Milder, same root.

Why this is worth its own card

The fix for each is a judgment about that layout (is a floor right, what value, should the row wrap), and each lives in a different package with its own build and test surface — so verifying them properly means a browser repro per header, which #7244's PR deliberately did not take on.

Suggested check

Drive a record whose header carries three or more labelled actions at a viewport around 800px through each path, and read h1.getBoundingClientRect().width against h1.scrollWidth, the way #7244 was measured.

Generated by Claude Code

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:ui / priority:p2 / pm:queue

    锚定 (anchoring):两个 header 分别在 packages/plugin-detail 和 packages/layout,都在 objectui 的 ui lane(published library)。虽然是两个包、两套 build,但都是 domain:ui,一张卡即可,不拆。

    在 origin/main a472b07 上复核的锚点 —— 主张成立,行号已漂移

    ⚠️ 卡片写的 DetailView.tsx:919,当前是 :960-976:

    packages/plugin-detail/src/DetailView.tsx:959     {schema.showHeader !== false && (
    packages/plugin-detail/src/DetailView.tsx:960     <div className="flex flex-col sm:flex-row items-start justify-between gap-3 sm:gap-4 pb-4 border-b">
    packages/plugin-detail/src/DetailView.tsx:961       <div className="flex items-start gap-2 sm:gap-3 flex-1 min-w-0">
    packages/plugin-detail/src/DetailView.tsx:972         <div className="flex-1 min-w-0">
    packages/plugin-detail/src/DetailView.tsx:974           <h1 className="text-xl sm:text-2xl font-bold truncate">
    

    卡片说的两件缺失都实测为真:外层行 :960 没有 flex-wrap(只有 sm:flex-row),title 列 :961 没有宽度下限(只有 flex-1 min-w-0)。:956-958 的注释也确认了卡片"这是 host 不给 page:header 时渲染的那个 header"—— 活路径,不是死代码。

    ⚠️ 卡片写的 PageHeader.tsx:236 恰好还是 236:

    packages/layout/src/PageHeader.tsx:218   <div className="flex flex-wrap items-center gap-x-4 gap-y-2">
    packages/layout/src/PageHeader.tsx:236     <div className="flex flex-col min-w-0 flex-1">
    packages/layout/src/PageHeader.tsx:242     {slot && <div className="flex items-center gap-2 ml-auto">{slot}</div>}
    

    卡片的"部分免疫"判断也成立::218 已有 flex-wrap,但 :236 的 title 列仍是 flex-1(flex-basis 0,永远不会撑出换行),而 :242 的 slot 带 ml-auto 挤在同一行 —— 同根、较轻。

    #7244 的修法已经落地,是现成的样板

    packages/components/src/renderers/layout/containers.tsx:1956   'flex flex-col sm:flex-row sm:flex-wrap items-start sm:items-center justify-between gap-3 sm:gap-4 pb-4 border-b',
    packages/components/src/renderers/layout/containers.tsx:1961   <div className="flex flex-col min-w-0 sm:min-w-48 flex-1">
    

    :1940-1942 还留了两条为什么这么改的注释。正控制:sm:min-w-48 全仓 git grep -c = 2,两处都在 containers.tsx —— 也就是说 #7244 的修法目前只此一家,本卡这两处确实没沾到。

    定级理由

    priority:p2:兄弟现场是实测的(799px 视口下 h1 被压到 29.5px),不是推测;DetailView 的 header 是 host 不自带 page:header 时的默认记录头,覆盖面比 page:header 那条更广。没有数据损坏也没有静默错答(视觉可见即可发现),所以不是 p1。

    给执行席的注意事项

    1. ⚠️ 不要照搬 sm:min-w-48 就收工。卡片自己说清楚了:每处的取值(要不要下限、多少、行要不要 wrap)是那套布局自己的判断。PageHeader 那条尤其 —— 它已经 wrap 了,缺的是"title 列贡献宽度",min-w 与把 flex-1 换成 flex-auto/给 basis 是两种不同的答案。
    2. 按卡片的"Suggested check"各做一次浏览器实测(三个以上带标签的 action、约 800px 视口,读 h1.getBoundingClientRect().width 对 h1.scrollWidth),与 RecordTitleChip: the record title collapses to ~30px ("S…") when the header carries three wide action buttons at ~800px #7244 同法取证 —— 这是卡片作者明说 PR RecordTitleChip: the record title collapses to ~30px ("S…") when the header carries three wide action buttons at ~800px #7244 故意没背的那部分工作,不要再往后推。
    3. 两个包 build/test 面不同,允许拆成两个 PR;若拆,第二个 PR 引用本卡而不是新开卡。

    ⛔ 分诊席不认领、不派单、不写码。


    Generated by Claude Code

  3. os-tesla commented on Sep 11, 2026

    @os-tesla
    Collaborator

    Claim: branch claude/issue-7281-record-header-width-arbitration

    Clause-②: no

    domain:ui PM seat, session_01UzHd6hDYatoDn17BuwKxnZ, R16. Assignee and this comment are set by the PM on the dev's behalf — the dev inherits both, posts no second claim, and never writes the assignee field.

    ⚠️ no with the reasoning stated, since the rule is 「拿不准 ⇒ 按 yes」 and a no may not be revised on delivery: this is a layout change — Tailwind classes on two existing headers. No declared type, registration input, exported symbol, authorable key or accept-set moves in either direction. ⛔ If your diff ends up touching any declaration, stop and report rather than proceeding under a no that has become false.

    ZONE 1 — what to build

    Two headers repeat the width-arbitration hazard objectui#7244 already fixed on page:header. The precedent fix was an sm:min-w-48 floor on the title column plus sm:flex-wrap on the row.

    1. packages/plugin-detail/src/DetailView.tsx — same shape, no protection. A flex row with a shrink-0 action tail and a flex-1 min-w-0 title column; the title column, being the only flexible item, absorbs the whole width deficit. objectui#7244 measured the sibling at a 799px viewport: header 687px, tail 641.5px, h1 left with 29.5px. ⭐ This is a live path — the file's own comment says this header renders when the host does not supply a page:header.

    2. packages/layout/src/PageHeader.tsx — partially protected. Its outer row already wraps, so the deficit has somewhere to go, but the title column is still flex-1 with no floor — and flex-1 means a flex-basis of 0, so the column never contributes width that would force a wrap, and a wide slot on the same line still takes the remainder. Milder, same root.

    ⚠️ Line numbers in the card (:919, :236) are from 2026-09-02 and are stale by default — re-derive them. ⚠️ And the card is explicit that it was identified statically, never measured in a browser: treat its geometry as a claim with a timestamp, not as a reading.

    ZONE 2 — the measurement this card actually turns on

    ⭐ This is the deliverable, not a nice-to-have. The card names the method, and it is the same one objectui#7244 was measured with:

    Drive a record whose header carries three or more labelled actions at a viewport around 800px through each path, and read h1.getBoundingClientRect().width against h1.scrollWidth.

    ⚠️ jsdom and happy-dom report clientWidth 0 — a standing lane fact. ⇒ a jsdom assertion about width is NOT MEASURED, not a pass. Use the real browser: Chromium is preinstalled at /opt/pw-browsers and PLAYWRIGHT_BROWSERS_PATH is already set. ⛔ Never run playwright install.

    ⛔ A scripted scroll or a resize you assert immediately is not a reachability measurement either — let layout settle before reading.

    Measure before and after on both headers. A fix whose before-reading was never taken cannot be shown to have fixed anything.

    ZONE 3 — fences

    • ⛔ Two headers only: DetailView.tsx and PageHeader.tsx. Do not sweep for a third.
    • ⛔ Do not touch containers.tsx / page:header — objectui#7244's surface, already fixed and shipped.
    • ⚠️ packages/plugin-detail also hosts PR objectui#9058, which is held and will rebase later — it touches RelatedList.tsx and renderers/record-related-list.tsx, ⛔ not DetailView.tsx. Stay out of those two files and there is no collision.
    • ⛔ Do not restructure either header's DOM. The precedent fix was two utility classes; if you find yourself changing the element tree, that is a different card.

    ZONE 4 — stop conditions, each stated as the harm it guards

    ⭐ Stated as harms deliberately — a stop condition naming a location instead of a harm cost this lane a round today, and the correction has since paid twice. If a location I give is wrong but the harm is untouched, keep going and say so in the report. PM claims are in scope for falsification, and a clean stop on a falsified PM premise is a full success.

    • Stop if the browser measurement does not reproduce the hazard on either header. The harm is shipping a floor nobody needed and calling a statically-guessed bug fixed. Report the readings; a falsified premise here is a full delivery.
    • Stop if a width floor would clip or overflow the action tail at a narrow viewport. The harm is trading a squeezed title for an unreachable button — strictly worse, since the title at least truncates visibly.
    • Report, don't stop, if the two headers want different floor values. They are different layouts; the card says as much. Say why, per header.

    ZONE 5 — acceptance

    • A pin per header asserting the measured geometry, not the class string: at the reproducing viewport, h1.getBoundingClientRect().width is above the floor and the action tail is fully within the header's box. ⛔ toHaveClass('sm:min-w-48') is not acceptance — it would pass against a floor that does nothing.
    • The unaffected wide-viewport case is unchanged (regression guard).
    • Ablate each floor and show its geometry assertion reds; restore and prove the tree clean by state (git diff HEAD empty + blob hash back to HEAD's), ⛔ never by an exit code.

    Generated by Claude Code

  4. self-assigned this
    on Sep 11, 2026
  5. os-tesla commented on Sep 11, 2026

    @os-tesla
    Collaborator

    os-dev-report

    {
      "issue": 7281,
      "status": "done",
      "branch": "claude/issue-7281-record-header-width-arbitration",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9120",
      "premise_still_valid": true,
      "summary": "Both headers reproduce the hazard in a real browser, so neither stop condition fired. Chromium, console dev server, three labelled record_header actions, layout settled before each read, viewport 799px: DetailView's own header (packages/plugin-detail/src/DetailView.tsx:1028-1030, re-derived; the card's :919 was stale) left the h1 at 6.17px of a 218px scrollWidth with a 724.83px shrink-0 tail on the same line - worse than the 29.5px objectui#7244 measured on the sibling. PageHeader (packages/layout/src/PageHeader.tsx:218/236, :236 still accurate) left the h1 at 170.59px of 265px. After: 218.39px and 751px, tail wrapped to its own line in both. The two headers DO want different floors and the PR says why per header: DetailView takes objectui#7244's two utilities sm:-scoped, but sm:min-w-64 rather than sm:min-w-48 because its title column carries the 40px back button plus a 12px gap INSIDE it, so 256px is what leaves the h1 the same ~192px readable floor; PageHeader needed only the floor and that floor is unconditional rather than sm:-scoped, because its row has no breakpoint-scoped direction change and the squeeze measures worse just below sm (639px: h1 10.59px of a 212px scrollWidth) - an sm:-scoped copy of the precedent would have left that reading untouched. min-w-48 replaces min-w-0 rather than joining it; an explicit min-width overrides the flex automatic min-content minimum exactly as min-w-0 did, verified by measuring a 92-character title at 799px (h1 751px of a 1682px scrollWidth, row still 799px, zero document overflow). No DOM restructuring: the whole change is three class strings. Clause-2 stayed no - nothing declared moved.",
      "tests": "MEASUREMENT (the deliverable, real Chromium at /opt/pw-browsers, never jsdom): before/after at 799px on both headers as above; narrow-viewport stop condition checked at 320/375/639px - action tail fully inside the header box and documentOverflowX 0 at every one; regression guard at 1440px identical before and after (DetailView titleCol 699.17px h1 218.39px unclipped, PageHeader 811.59px unclipped, tail on the title's line in both); below sm DetailView is byte-identical before/after (computed min-width still 0px). PIN: e2e/record-header-title-width.spec.ts, 5 tests, geometry not class strings - h1 getBoundingClientRect().width must clear the 192px floor and the tail must stay inside the header box; row/title-column/tail/h1 class strings are read out of the two sources at run time and compiled with the installed Tailwind, each extraction anchored and validated against a token that is not the floor under test (that guard actually fired during development). Default playwright.config.ts picks all 5 up: playwright test --list --project=chromium prints 42 tests in 8 files, 5 of them this file, so CI's pnpm test:e2e --project=chromium runs it. ABLATION, each with on-disk proof and restore proven BY STATE (git diff HEAD empty AND blob hash back to HEAD's, never an exit code): remove sm:min-w-64 -> 'h1 is 9px of a 402px title; title column 61px, tail 722px' expected >= 192; remove sm:flex-wrap -> 'tail escaped the header box (row right 799)'; PageHeader min-w-48 back to min-w-0 -> 'h1 is 143px of a 479px title' expected >= 192. Each reds exactly one test and leaves the roomy-viewport guard green. Blobs restored to 30f5650 / 1e7f086. One earlier ablation ran VOID and is reported rather than quietly re-run: a mis-written guard skipped its restore, so the next ablation ran doubly-mutated and died as a wait timeout instead of a geometry assertion; both redone from a clean tree, and the settle-wait was fixed so a collapsed h1 reads as 0 instead of hiding behind a TimeoutError. GATES on f96f4f5, every exit code captured before any pipe: vitest packages/plugin-detail 167 files 1562 tests passed; vitest packages/layout 21 files 217 tests passed; the objectui#7244 sibling pin 6 passed; turbo type-check for both packages 14/14; pnpm type-check:e2e pass with tsc --listFiles confirming the new spec is IN the project (1 hit, not excluded); eslint on the three changed files 0 errors, 0 output for the new file; check-changeset-presence/-fixed/-no-major pass; check-control-bytes pass plus a direct grep -naP control-byte scan of the four files, 0 hits; check-governed-queue-guard --test says NOT GOVERNED (4 paths, none matched); check-unreferenced-sources, check-vi-mock-specifiers, check-type-check-coverage, check-new-cross-file-line-citations all pass. Heavy runs went through /home/user/objectstack/scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=objectui-7281. NOT MEASURED: the full pnpm test and pnpm lint farm runs, and the CI e2e leg itself (that leg builds and previews the console, which the local narrowing avoided) - all declared to CI.",
      "mcp_calls": "5 - search_issues x2 (one lit control, one dedup), issue_write x1, create_pull_request x1, add_issue_comment x1. Issue body and all 8 comments were read through the zero-quota web payload channel; dedup also used one repo-scoped REST list (labels domain:ui + priority:p2, 81 rows, no next link, lit control #7281 present). A first REST attempt with labels=domain:ui alone FAILED its lit control (100 rows, next link present, #7281 absent) and was discarded rather than read as a zero.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #9119: DetailView's own record header sizes its title column to the full title text below the sm breakpoint, so a long record name pushes the page sideways - measured at 375px, title column 1201.22px inside a 375px row, 826px of horizontal document overflow; a cross-axis size in a column flex container where min-w-0 is a floor and not a ceiling, distinct from this card's arbitration; measured identical on d8b4739 and on this branch, so pre-existing and untouched by this change",
        "noted, not filed: packages/components/src/__tests__/page-header-title-width.test.tsx is class-shape only and admits so in its own docblock, so it would pass against an inert floor; now that a browser geometry lane exists in e2e/ its assertions could be upgraded the same way. Successor: the next PR on objectui#7244's page:header surface - that file is objectui#7244's surface and outside this card's fences, so not touched here."
      ]
    }

    Generated by Claude Code

  6. removed their assignment
    on Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions