Skip to content

fix(web): capitalize project name display across all five sites#2301

Open
neogenix wants to merge 9 commits into
nexu-io:mainfrom
eefynet:fix/web-design-files-title-case
Open

fix(web): capitalize project name display across all five sites#2301
neogenix wants to merge 9 commits into
nexu-io:mainfrom
eefynet:fix/web-design-files-title-case

Conversation

@neogenix
Copy link
Copy Markdown

Why

Project names rendered all-lowercase across the design-files page header
and every other site that displays them, including the workspace tab
bar, the workspace tab overflow popover, the designs-tab cards, and the
recent-projects strip. The stored value is whatever the user typed at
project-create time; the display layer was just not styling it.

The original scope of this fix targeted only the design-files page
header. During the 10-pass adversarial review, a reviewer identified
that capitalizing only one of five sites produces worse visual
consistency than not capitalizing any: a user with project acme studio
would see Acme Studio on the design-files page and acme studio
everywhere else. The fix was extended to cover all five sites.

What users will see

Project names render with capitalized first letters in:

  • Design files page header (.app-project-title .title)
  • Workspace tab bar labels (.workspace-tab__label)
  • Workspace tab overflow popover (.workspace-tabs-list__title)
  • Designs tab cards (.design-card-name)
  • Recent projects strip on Home (.recent-projects__card-name)

This is a CSS text-transform: capitalize rule, so the underlying
stored value is unchanged — a project named acme studio is still
stored as acme studio and exported as acme studio. Only the
rendered glyphs change.

Surface area

  • UI — five existing display sites for the project name

No i18n keys added (CSS-only). No CLI surface (pure display layer).
No data-shape change (storage untouched).

Screenshots

The fix is a one-glyph-per-word change visible only when a project name
has lowercase characters. Reproduction: create a project named
acme studio; on main, every display site shows acme studio; on
this branch, every display site shows Acme Studio while the stored
name remains acme studio.

Bug fix verification

  • Vitest specs:
    • apps/web/tests/components/ProjectView.title-casing.test.tsx
      mounts ProjectView with a lowercase name, injects the relevant
      rules from index.css into the test JSDOM, and asserts
      getComputedStyle(titleEl).textTransform === 'capitalize'.
    • apps/web/tests/components/css-project-name-casing.test.tsx
      same assertion technique applied to each of the other four
      selectors (full-component mount for WorkspaceTabsBar,
      DesignsTab, RecentProjectsStrip; fixture DOM for the tab
      overflow list, since its visibility depends on viewport width).
    • All specs verified red on main (rules absent) and green on this
      branch (rules present).
  • Playwright spec e2e/ui/project-name-casing.test.ts (6 tests):
    creates a real project named 'acme studio' via POST /api/projects,
    navigates to each display site, and asserts via real-browser
    getComputedStyle that text-transform resolves to capitalize.
    Also asserts the stored textContent remains lowercase (proving the
    fix is display-only). Tests 2-5 red on main; all 6 green here.

Validation

  • pnpm guard (clean)
  • pnpm typecheck (clean)
  • pnpm --filter @open-design/web test ProjectView.title-casing
    — 1/1 pass
  • pnpm --filter @open-design/web test css-project-name-casing
    — 4/4 pass
  • pnpm exec playwright test -c playwright.config.ts project-name-casing
    — 6/6 pass (browser-verified in Chromium, 11.0s)

Trade-off documented

text-transform: capitalize only touches the first character of each
whitespace-separated word; it does not lowercase subsequent characters.
That means a brand-name project like iPhone Studio displays as
IPhone Studio rather than iPhone Studio. This is acceptable for the
primary case I reported (auto-generated lowercase project names) and is
documented inline in index.css near the rule. The storage-layer
alternative — preserving user-typed casing exactly on create/rename —
would be more correct for brand names but is a much larger change with
its own data-migration concerns; flagged as a follow-up in the inline
comment.

Adjacent issues (out of scope)

  • Storage-layer casing preservation (would supersede this CSS approach
    for brand names; intentionally deferred per the trade-off note).
  • The existing string-based test at apps/web/tests/styles/... was
    removed during the 10-pass cleanup in favor of the behavior-level
    specs above. That was technical-debt cleanup folded into the same
    PR scope.

Coordination note

There is an open upstream PR #2258 ("Fix (web): long project name
rendering") that also touches apps/web/src/components/ProjectView.tsx
and apps/web/src/index.css in adjacent blocks (it adds a
title={project.name} tooltip attribute and small padding/margin
tweaks). The diffs do not overlap on the same lines and merge
mechanically. The two PRs are functionally complementary — that one
makes long names readable on hover, this one makes lowercase names
readable on first glance.

neogenix added 6 commits May 19, 2026 13:21
Add text-transform: capitalize to .app-project-title .title so
lowercase-stored project names display with capitalized first
letters in the design files page header.

Red spec: apps/web/tests/styles/project-title-casing.test.ts
Red on origin/main, green on this branch.
Adds a jsdom render test that mounts ProjectView with a lowercase project
name ('acme studio'), injects the .app-project-title CSS rules from
index.css into the document head, and asserts that getComputedStyle on
the data-testid='project-title' element returns text-transform: capitalize.

Red/green verified: removing the text-transform rule produces
  AssertionError: expected 'none' to be 'capitalize'
Restoring the rule makes the test pass.
The project-title-casing.test.ts in tests/styles/ was a static CSS parser
that asserted the same text-transform: capitalize property already covered
by the behavior test in tests/components/. Two tests asserting one property
is technical debt; delete the simpler one.

Also replace the process.cwd()-relative readFileSync path with
path.resolve(process.cwd(), ...) and add an explanatory comment. In the
jsdom environment import.meta.url is not a file: URL, so process.cwd() is
the correct anchor. The original code worked but was undocumented.
text-transform: capitalize is a display-layer workaround for auto-generated
all-lowercase project names. Brand names with mid-word capitalisation
(e.g. iPhone -> IPhone) will be displayed incorrectly. A future storage-layer
fix that preserves input casing on create/rename should remove this rule.
Add text-transform: capitalize to the four additional CSS selectors that
render user project names, matching the rule already applied to the
design-files page header (.app-project-title .title):

- .workspace-tab__label       — tab strip in WorkspaceTabsBar
- .workspace-tabs-list__title — tab overflow popover list
- .design-card-name           — projects grid and live-artifact cards in DesignsTab
- .recent-projects__card-name — recent-projects strip on the Home view

The rule for .recent-projects__card-name lives in
src/styles/home/recent-projects.css (the four index.css selectors are
co-located with their existing rules).

All four selectors also render non-project-name text in some contexts
(navigation labels like 'Home', artifact titles). The capitalize rule is
safe for those: already-capitalized strings are unaffected, and the
brand-name trade-off documented on .app-project-title .title in index.css
applies equally here.

Tests: add apps/web/tests/components/css-project-name-casing.test.tsx
covering all four new sites with the same stylesheet-injection pattern
used for the design-files header test. Each test is verified RED on
origin/main (CSS rules absent) and GREEN on this branch.

Also extend extractProjectTitleRules in ProjectView.title-casing.test.tsx
to a general selector list covering all five sites.
@lefarcen lefarcen requested a review from mrcfps May 19, 2026 19:43
@lefarcen lefarcen added size/XL PR changes 700-1500 lines risk/medium Medium risk: regular code changes type/bugfix Bug fix labels May 19, 2026
@lefarcen lefarcen requested a review from qiongyu1999 May 19, 2026 19:44
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b844d61849

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/index.css
Comment on lines +8571 to +8573
/* Same display-layer capitalize as .app-project-title .title — see that rule for the
brand-name trade-off note. Applies to project names and live-artifact titles. */
text-transform: capitalize;
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 Badge Scope capitalization to project cards only

This rule changes .design-card-name globally, but that class is used for both project names and live-artifact titles in DesignsTab (the live-artifact card renders artifact.title with the same class). That means a fix intended for project-name display also rewrites unrelated artifact titles, so mixed-case or intentionally styled artifact names are shown incorrectly. Please scope this transform to project-name rendering only (or split the class) so artifact titles keep user-provided casing.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm not sure this is a bad thing. I'm open to changing it, but we might want to either split out the class, or apply the fix universally.

Copy link
Copy Markdown
Contributor

@mrcfps mrcfps left a comment

Choose a reason for hiding this comment

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

Thanks for pushing this through. I found two places where the new shared selectors still rewrite non-project text, so I left inline notes on the spots that need narrower scoping.

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

Comment thread apps/web/src/index.css
Comment on lines +669 to +671
/* Same display-layer capitalize as .app-project-title .title — see that rule for the
brand-name trade-off note. Applies to all tab types (project, home, marketplace). */
text-transform: capitalize;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These shared tab-label selectors also style non-project tabs. WorkspaceTabsBar.displayTabFor() feeds the same elements entry and marketplace titles like entry.navDesignSystems and workspaceTabs.marketplace, so this rule will retitle those localized labels too (Design systemsDesign Systems, etc.). That broadens the fix beyond project names and can produce incorrect copy in locales that intentionally do not capitalize every word. Please scope the transform to project tabs only—for example by adding a project-only modifier class when tab.kind === project and moving text-transform onto that class instead of the shared tab-label selectors.

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

Comment thread apps/web/src/index.css
Comment on lines +8571 to +8573
/* Same display-layer capitalize as .app-project-title .title — see that rule for the
brand-name trade-off note. Applies to project names and live-artifact titles. */
text-transform: capitalize;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

.design-card-name is shared by both project cards and live-artifact cards in DesignsTab—the live-artifact branch renders artifact.title with this same class. With text-transform here, arbitrary artifact titles now get rewritten too, so values with intentional casing (for example brand names or version strings) display incorrectly even though this PR is meant to touch project names only. Please split the project-name styling from the shared card-title class, or add a project-only class on the project-card path and leave artifact titles on the existing selector without text-transform.

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm debating how best to respond to the feedback. The fix should probably be applied to all of them, but the feedback is correct given the naming / usage / etc... I'll noodle on it, and likely focus on the individual fix.

Copy link
Copy Markdown
Contributor

@mrcfps mrcfps left a comment

Choose a reason for hiding this comment

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

Thanks for tightening the selector scoping here. I found one blocker in the new Playwright coverage: the last case still asserts the old shared capitalize behavior on entry tabs.

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

Comment thread e2e/ui/project-name-casing.test.ts Outdated
@neogenix
Copy link
Copy Markdown
Author

Fixed in 399133f: flipped the nav-tab textTransform assertion in e2e/ui/project-name-casing.test.ts from toBe('capitalize') to not.toBe('capitalize') for both the Home and Projects entry tabs, and updated the test name/description to reflect the new invariant (scoping guard rather than idempotency check).

Copy link
Copy Markdown
Contributor

@mrcfps mrcfps left a comment

Choose a reason for hiding this comment

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

Thanks for the follow-up here. I found one remaining Designs tab path where project names still skip the new capitalize styling, so I left the concrete spot inline.

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

<div className="design-card-meta-block">
<ProjectTag category={projectCategory(p)} />
<div className="design-card-name" title={p.name}>
<div className="design-card-name design-card-name--project" title={p.name}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice catch scoping the grid-card selector. One remaining path in this same component still bypasses the new rule: the kanban branch renders project names with .design-kanban-card-name at DesignsTab.tsx:716-720, and apps/web/src/index.css:9063-9069 still has no text-transform for that selector. That means a project called acme studio is fixed in grid view but flips back to lowercase as soon as the user switches the Designs tab to kanban, which is the same consistency bug this PR is trying to remove. Please apply the same project-name casing treatment to the kanban card title path as well, and extend the Vitest/Playwright coverage to exercise the kanban view so this view-mode regression is caught next time.

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

The original five-site sweep missed the kanban view's
.design-kanban-card-name selector at DesignsTab.tsx:716-720. Project
names rendered correctly in the grid view (via .design-card-name--project)
but flipped back to lowercase the moment the user switched to kanban.

apps/web/src/index.css gains text-transform: capitalize on
.design-kanban-card-name. The selector is project-name-only by usage
(kanban renders only project cards; live artifacts go through
.design-card-name in the grid view), so no per-card modifier class is
needed.

e2e/ui/project-name-casing.test.ts gains a sixth scenario that toggles
DesignsTab to kanban via [data-testid=designs-view-kanban] and asserts
the .design-kanban-card-name computed text-transform resolves to
'capitalize'. Header comment updated from five selectors to six.
@neogenix
Copy link
Copy Markdown
Author

Fixed in 00e08f2: applied text-transform: capitalize to .design-kanban-card-name in apps/web/src/index.css (kanban view renders only project cards, so the selector is naturally project-scoped — no per-card modifier needed). Extended e2e/ui/project-name-casing.test.ts with a sixth scenario that toggles DesignsTab to kanban via [data-testid=designs-view-kanban] and asserts the computed text-transform resolves to capitalize. Header comment updated from five selectors to six.

@neogenix neogenix requested a review from mrcfps May 19, 2026 21:32
Copy link
Copy Markdown
Contributor

@mrcfps mrcfps left a comment

Choose a reason for hiding this comment

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

@neogenix I re-checked the selector scoping and the follow-up coverage on this head: project-only modifier classes are now used for the tab labels and grid cards, the kanban card path has its own capitalize rule, and the updated regression tests cover both the project-name paths and the non-project tab guard. Thanks for iterating on the follow-up fixes 🙌

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

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

Labels

risk/medium Medium risk: regular code changes size/XL PR changes 700-1500 lines type/bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants