Skip to content

cmux NEXT Settings: a group title never repeats its page's title (Updates, Advanced) - #28665

Merged
teamleaderleo merged 7 commits into
feat-cmux-nextfrom
lane/hunt-settings-dup-title
Nov 1, 2026
Merged

teamleaderleo merged 7 commits into
feat-cmux-nextfrom
lane/hunt-settings-dup-title

Conversation

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Leo, 2026-10-30, on Settings > Updates: "look for wastage".

Settings > Updates drew its title twice: "Updates" as the page title, then "Updates" again over its first group, then "Updates: Advanced" over the second. design/settings.md lists it under Not yet settled ("A group whose title repeats its page's title is a candidate for the same waste rule").

What changes

  • On a category page, a group whose title is the page's own title draws no title. A group whose title starts with the page title and a colon drops that prefix ("Updates: Advanced" reads "Advanced").
  • Search results keep each group's whole title. In search, the title is the only thing that says which page a row comes from.
  • Rows, groups, their order, the schema and the Swift layout don't change (GroupList shownGroupTitle, the page title passed from SectionView). Updates is the only page today whose group title repeats its page title. The DOM test checks every category page so a new one is caught.

Tests (red at the first commit, green at the head)

  • webviews/src/pages/settings/groupTitles.test.tsx: the rule's cases; Updates draws ["Advanced"] only; no category page draws a group title equal to its own title; search keeps "Updates".
  • webviews/test/settings-group-titles-browser.test.ts: Chromium on the gallery's pages.settings variant updates. It checks the visible group titles and writes 2x light and dark stills.

Stills

Gallery (pages.settings, updates), Chromium, synthetic data, light and dark: to follow from the cmux-ci runs at the red commit (before) and the head (after).

Observed / Not run

  • Observed: lints on the changed files (vp fmt, browser-lane check). web-lint's other failures are base debt in files this PR doesn't touch (check-design-tokens and check-layers on pages/settings/styles.css, vp fmt on the context-menu files).
  • Not run yet: the two tests (they go to cmux-ci; no local heavy runs on this VM).

Changelog

Changed: Settings > Updates no longer repeats "Updates" over its groups; the advanced group reads "Advanced".

Design spec: leo-workspace design/settings.md (moves its first "Not yet settled" item into Rules in a follow-up).

Agent session: agent session

🤖 Generated with Claude Code

teamleaderleo and others added 2 commits October 31, 2026 01:11
Settings > Updates draws "Updates" as its page title and again as its
first group's title, then "Updates: Advanced". A DOM test over every
category page and a Chromium gallery test (pages.settings, variant
updates) that writes light and dark stills. Red on base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
Leo, 2026-10-30, on Settings > Updates: "look for wastage". The page
drew "Updates" twice (page title, then its first group's title) and
"Updates: Advanced" for the second group. On a category page a group
whose title is the page's title draws no title, and one that starts with
the page's title and a colon drops that prefix ("Advanced"). Search
results keep each group's whole title. Rows, groups and the schema are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
@github-actions github-actions Bot added the risk:low Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@github-actions github-actions Bot added risk:normal Risk tier (docs/ci/risk-tiers.md) and removed risk:low Risk tier (docs/ci/risk-tiers.md) labels Oct 31, 2026
@blacksmith-sh

blacksmith-sh Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Found 6 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp3n1s8i7q/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmpp6o7ai7n/summary-fail.json (live):/summary-fail.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent opus-5.5 medium, from the nightly-land helper): SHIP at e229a25

Follow-ups:

  • [follow-up] A first group under cards with no group header may look merged into the card above it; check the gallery stills.
  • [follow-up] The category sweep test should also assert that no drawn group title starts with ":".
  • [follow-up] toLocaleLowerCase can change string length (e.g. "İ"), which can shift the prefix slice; the fallback keeps the full title, so no harm done.

That review is at e229a25. The branch then merged base cleanly (2ad6dd1), and an independent review runs at the new head.

@teamleaderleo
teamleaderleo marked this pull request as ready for review November 1, 2026 01:16
@cursor

cursor Bot commented Nov 1, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex-review-box/gpt-6-astra high, lane-place): SHIP at 2ad6dd1

SHIP at 2ad6dd1
Visual verdict: no images; Leo tries the build

  1. [P2] [follow-up] review/pr.body:15 — Attach the promised light/dark screenshots to verify spacing and heading hierarchy.

No blocking code findings. All eight helper cases passed under Node. DOM/browser suites were not run: Bun and dependencies are unavailable.

Follow-ups:

  • Attach light/dark screenshots of Settings > Updates.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="webviews/src/pages/settings/components/GroupList.tsx">

<violation number="1" location="webviews/src/pages/settings/components/GroupList.tsx:25">
P2: The prefix removal uses the lowercased page length as an offset into the original title. Case mappings such as `İ` can change string length, so valid localized `page: group` titles fail to lose their page prefix; keep the trimmed original page text for slicing and use a separate lowercased value only for comparison.</violation>

<violation number="2" location="webviews/src/pages/settings/components/GroupList.tsx:29">
P2: The prefix stripper misses the Khmer colon used by the shipped `updatesAdvanced` translation, so Khmer still renders `Updates`' equivalent twice. Include `៖` in the accepted separators.</violation>
</file>

<file name="webviews/src/pages/settings/groupTitles.test.tsx">

<violation number="1" location="webviews/src/pages/settings/groupTitles.test.tsx:57">
P3: This all-category check is vacuous when a route fails to render the requested category because a missing section becomes an empty title list. Assert the section exists before inspecting its group titles.</violation>

<violation number="2" location="webviews/src/pages/settings/groupTitles.test.tsx:69">
P2: This assertion does not verify that search matched an Updates row: `SearchResults` renders every group heading, including collapsed nonmatches. Assert the matching Updates row and its containing group title so a broken search cannot still pass this test.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment on lines +25 to +29
const page = pageTitle.trim().toLocaleLowerCase();
const own = title.trim();
if (own.toLocaleLowerCase() === page) return "";
const head = own.slice(0, page.length).toLocaleLowerCase();
const rest = own.slice(page.length).match(/^\s*[::]\s*(.+)$/u);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The prefix removal uses the lowercased page length as an offset into the original title. Case mappings such as İ can change string length, so valid localized page: group titles fail to lose their page prefix; keep the trimmed original page text for slicing and use a separate lowercased value only for comparison.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/pages/settings/components/GroupList.tsx, line 25:

<comment>The prefix removal uses the lowercased page length as an offset into the original title. Case mappings such as `İ` can change string length, so valid localized `page: group` titles fail to lose their page prefix; keep the trimmed original page text for slicing and use a separate lowercased value only for comparison.</comment>

<file context>
@@ -14,23 +14,43 @@ const FIRST_FRAME_ROWS = 24;
+ */
+export function shownGroupTitle(title: string, pageTitle?: string): string {
+  if (!pageTitle) return title;
+  const page = pageTitle.trim().toLocaleLowerCase();
+  const own = title.trim();
+  if (own.toLocaleLowerCase() === page) return "";
</file context>
Suggested change
const page = pageTitle.trim().toLocaleLowerCase();
const own = title.trim();
if (own.toLocaleLowerCase() === page) return "";
const head = own.slice(0, page.length).toLocaleLowerCase();
const rest = own.slice(page.length).match(/^\s*[::]\s*(.+)$/u);
const pageText = pageTitle.trim();
const page = pageText.toLocaleLowerCase();
const own = title.trim();
if (own.toLocaleLowerCase() === page) return "";
const head = own.slice(0, pageText.length).toLocaleLowerCase();
const rest = own.slice(pageText.length).match(/^\s*[::]\s*(.+)$/u);

page = await renderPage({ path: "/settings/general" });
await changeValue(page.container.querySelector<HTMLInputElement>("[data-settings-search]")!, "update channel");
await settle();
expect(groupTitles(page.container.querySelector("[data-search-results]"))).toContain("Updates");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This assertion does not verify that search matched an Updates row: SearchResults renders every group heading, including collapsed nonmatches. Assert the matching Updates row and its containing group title so a broken search cannot still pass this test.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/pages/settings/groupTitles.test.tsx, line 69:

<comment>This assertion does not verify that search matched an Updates row: `SearchResults` renders every group heading, including collapsed nonmatches. Assert the matching Updates row and its containing group title so a broken search cannot still pass this test.</comment>

<file context>
@@ -0,0 +1,70 @@
+  page = await renderPage({ path: "/settings/general" });
+  await changeValue(page.container.querySelector<HTMLInputElement>("[data-settings-search]")!, "update channel");
+  await settle();
+  expect(groupTitles(page.container.querySelector("[data-search-results]"))).toContain("Updates");
+});
</file context>
Suggested change
expect(groupTitles(page.container.querySelector("[data-search-results]"))).toContain("Updates");
const updatesGroup = page.container.querySelector(
'[data-search-results] [data-section="updates"] [data-group="settings.group.updates"]',
);
expect(updatesGroup?.querySelector('[data-row-key="updates.channel"]')).not.toBeNull();
expect(updatesGroup?.querySelector(".group-title")?.textContent).toBe("Updates");

const own = title.trim();
if (own.toLocaleLowerCase() === page) return "";
const head = own.slice(0, page.length).toLocaleLowerCase();
const rest = own.slice(page.length).match(/^\s*[::]\s*(.+)$/u);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The prefix stripper misses the Khmer colon used by the shipped updatesAdvanced translation, so Khmer still renders Updates' equivalent twice. Include ៖ in the accepted separators.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/pages/settings/components/GroupList.tsx, line 29:

<comment>The prefix stripper misses the Khmer colon used by the shipped `updatesAdvanced` translation, so Khmer still renders `Updates`' equivalent twice. Include `៖` in the accepted separators.</comment>

<file context>
@@ -14,23 +14,43 @@ const FIRST_FRAME_ROWS = 24;
+  const own = title.trim();
+  if (own.toLocaleLowerCase() === page) return "";
+  const head = own.slice(0, page.length).toLocaleLowerCase();
+  const rest = own.slice(page.length).match(/^\s*[::]\s*(.+)$/u);
+  return head === page && rest ? rest[1] : own;
+}
</file context>
Suggested change
const rest = own.slice(page.length).match(/^\s*[::]\s*(.+)$/u);
const rest = own.slice(page.length).match(/^\s*[::៖]\s*(.+)$/u);

page = await renderPage({ path: `/settings/${category.id}` });
await settle();
const title = text(category.title).toLocaleLowerCase();
const titles = groupTitles(page.container.querySelector(`[data-section="${category.id}"]`));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This all-category check is vacuous when a route fails to render the requested category because a missing section becomes an empty title list. Assert the section exists before inspecting its group titles.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/pages/settings/groupTitles.test.tsx, line 57:

<comment>This all-category check is vacuous when a route fails to render the requested category because a missing section becomes an empty title list. Assert the section exists before inspecting its group titles.</comment>

<file context>
@@ -0,0 +1,70 @@
+    page = await renderPage({ path: `/settings/${category.id}` });
+    await settle();
+    const title = text(category.title).toLocaleLowerCase();
+    const titles = groupTitles(page.container.querySelector(`[data-section="${category.id}"]`));
+    expect({ page: category.id, repeats: titles.filter((t) => t?.toLocaleLowerCase() === title) }).toEqual({
+      page: category.id,
</file context>

@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 02:05 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 02:08 Inactive
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Gallery before/after for 604787a: 16 states rendered, 2 images differ, 267 more touched states not rendered. Images are in the run's artifact, not on this public PR; the independent review of this head reads them on its own, so the PR needs no hand-made screenshots of these states.

gh run download 41327283423 -R manaflow-ai/cmux -n gallery-advisory-28665-604787a9ba876d7cf6f682c6a81aa0b1255d5fee (then read gallery-review.json)

States
State Theme Status Diff Picked by
pages.settings/general Apple System Colors Light unchanged 0% own GroupList.tsx
pages.settings/general Apple System Colors unchanged 0% own GroupList.tsx
pages.settings/general-customized Apple System Colors Light unchanged 0% own GroupList.tsx
pages.settings/general-customized Apple System Colors unchanged 0% own GroupList.tsx
pages.settings/general-group-1 Apple System Colors Light unchanged 0% own GroupList.tsx
pages.settings/general-group-1 Apple System Colors unchanged 0% own GroupList.tsx
pages.settings/general-group-2 Apple System Colors Light unchanged 0% own GroupList.tsx
pages.settings/general-group-2 Apple System Colors unchanged 0% own GroupList.tsx
pages.settings/general-group-3 Apple System Colors Light unchanged 0% own GroupList.tsx
pages.settings/general-group-3 Apple System Colors unchanged 0% own GroupList.tsx
pages.settings/general-group-4 Apple System Colors Light unchanged 0% own GroupList.tsx
pages.settings/general-group-4 Apple System Colors unchanged 0% own GroupList.tsx
pages.settings/general-group-5 Apple System Colors Light unchanged 0% own GroupList.tsx
pages.settings/general-group-5 Apple System Colors unchanged 0% own GroupList.tsx
pages.settings/general-group-6 Apple System Colors Light unchanged 0% own GroupList.tsx
pages.settings/general-group-6 Apple System Colors unchanged 0% own GroupList.tsx
pages.settings-motion/play-search Apple System Colors Light changed 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-search Apple System Colors changed 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-changed-only Apple System Colors Light unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-changed-only Apple System Colors unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-override-reset Apple System Colors Light unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-override-reset Apple System Colors unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-reset-newtabposition Apple System Colors Light unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-reset-newtabposition Apple System Colors unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-reset-fontfamily Apple System Colors Light unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-reset-fontfamily Apple System Colors unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-reset-hibernation Apple System Colors Light unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-reset-hibernation Apple System Colors unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-reset-hibernationexclusions Apple System Colors Light unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-reset-hibernationexclusions Apple System Colors unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-reset-pinned Apple System Colors Light unchanged 0% imports GroupList.tsx (1 hops)
pages.settings-motion/play-reset-pinned Apple System Colors unchanged 0% imports GroupList.tsx (1 hops)

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

Passes: CI passes on 604787a9ba.

CI passes on 604787a9ba (run 41327283530 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on fe90490 to compare; do not merge until green; see lane-rules

Shipped in:

@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 06:37 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 06:42 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 10:21 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 10:22 Inactive
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (next-review patch-id; carried): SHIP at ad7da75

The independent SHIP at 2ad6dd1 carries to this head: the PR's own change (git diff $(git merge-base feat-cmux-next HEAD) HEAD) has the same git patch-id --verbatim (ad07e87134c9d54b) at both heads, and no merge between them was resolved by hand. Nothing new to review. A newer BLOCK, or any change to the PR's own diff, ends the carry.

@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 11:51 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 11:52 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 16:37 Inactive
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (next-review patch-id; carried): SHIP at 604787a

The independent SHIP at 2ad6dd1 carries to this head: the PR's own change (git diff $(git merge-base feat-cmux-next HEAD) HEAD) has the same git patch-id --verbatim (ad07e87134c9d54b) at both heads, and no merge between them was resolved by hand. Nothing new to review. A newer BLOCK, or any change to the PR's own diff, ends the carry.

@teamleaderleo
teamleaderleo merged commit 8fb4e8d into feat-cmux-next Nov 1, 2026
172 of 175 checks passed
@teamleaderleo
teamleaderleo deleted the lane/hunt-settings-dup-title branch November 1, 2026 18:36

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 604787a9 Deployed Nov 1, 2026 by cmux-bot[bot] via cmux-tui tree preflight #96886
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant