Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/src/components/DesignsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ export function DesignsTab({
</div>
<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.

{p.name}
</div>
<div className="design-card-meta">
Expand Down
176 changes: 88 additions & 88 deletions apps/web/src/components/WorkspaceTabsBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ export function WorkspaceTabsBar({ route, projects }: Props) {
<span className="workspace-tab__icon" aria-hidden>
<Icon name={display.icon} size={14} />
</span>
<span className="workspace-tab__label">{display.title}</span>
<span className={`workspace-tab__label${tab.kind === 'project' ? ' workspace-tab__label--project' : ''}`}>{display.title}</span>
</button>
<button
type="button"
Expand All @@ -495,94 +495,94 @@ export function WorkspaceTabsBar({ route, projects }: Props) {
</div>
);
})}
</div>
<div className="workspace-tabs-actions" ref={menuRef}>
<button
type="button"
className="workspace-tabs-new-btn"
onClick={createNewTab}
title="New tab"
aria-label="New tab"
>
<Icon name="plus" size={14} />
</button>
<button
type="button"
className={`workspace-tabs-icon-btn${tabsMenuOpen ? ' is-active' : ''}`}
onClick={() => setTabsMenuOpen((open) => !open)}
title="Search tabs"
aria-label="Search tabs"
aria-haspopup="dialog"
aria-expanded={tabsMenuOpen}
>
<Icon name="search" size={15} />
</button>
{tabsMenuOpen && typeof document !== 'undefined'
? createPortal(
<div
className="workspace-tabs-popover"
role="dialog"
aria-label="Search tabs"
ref={popoverRef}
>
<div className="workspace-tabs-search">
<Icon name="search" size={14} />
<input
ref={searchInputRef}
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search tabs"
aria-label="Search tabs"
/>
</div>
<div className="workspace-tabs-popover__section">
<span>Open tabs</span>
<span>{state.tabs.length}</span>
</div>
<div className="workspace-tabs-list" role="listbox" aria-label="Open tabs">
{filteredTabs.length > 0 ? (
filteredTabs.map((display) => {
const active = display.id === state.activeTabId;
return (
<div
key={display.id}
className={`workspace-tabs-list__item${active ? ' is-active' : ''}`}
role="option"
aria-selected={active}
<div className="workspace-tabs-actions" ref={menuRef}>
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.

workspace-tabs-actions is now nested inside .workspace-tabs-strip, but the strip is the horizontally scrollable container (apps/web/src/index.css:571-592 sets overflow-x: auto and flex: 1 1 0). That means the New tab / Search tabs controls become part of the scrollable tab content instead of staying pinned beside it as they did before this diff. Once a user opens enough tabs to overflow the strip, those controls can scroll off-screen and stop being immediately reachable, which regresses the primary tab-management path in the same area this component is trying to improve. Please move workspace-tabs-actions back out so it is a sibling of workspace-tabs-strip again, and keep only the tab pills inside the scrollable strip.

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

<button
type="button"
className="workspace-tabs-new-btn"
onClick={createNewTab}
title="New tab"
aria-label="New tab"
>
<Icon name="plus" size={14} />
</button>
<button
type="button"
className={`workspace-tabs-icon-btn${tabsMenuOpen ? ' is-active' : ''}`}
onClick={() => setTabsMenuOpen((open) => !open)}
title="Search tabs"
aria-label="Search tabs"
aria-haspopup="dialog"
aria-expanded={tabsMenuOpen}
>
<Icon name="search" size={15} />
</button>
{tabsMenuOpen && typeof document !== 'undefined'
? createPortal(
<div
className="workspace-tabs-popover"
role="dialog"
aria-label="Search tabs"
ref={popoverRef}
>
<div className="workspace-tabs-search">
<Icon name="search" size={14} />
<input
ref={searchInputRef}
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search tabs"
aria-label="Search tabs"
/>
</div>
<div className="workspace-tabs-popover__section">
<span>Open tabs</span>
<span>{state.tabs.length}</span>
</div>
<div className="workspace-tabs-list" role="listbox" aria-label="Open tabs">
{filteredTabs.length > 0 ? (
filteredTabs.map((display) => {
const active = display.id === state.activeTabId;
return (
<div
key={display.id}
className={`workspace-tabs-list__item${active ? ' is-active' : ''}`}
role="option"
aria-selected={active}
>
<button
type="button"
className="workspace-tabs-list__main"
onClick={() => openTab(display.tab)}
>
<button
type="button"
className="workspace-tabs-list__main"
onClick={() => openTab(display.tab)}
>
<span className="workspace-tabs-list__icon" aria-hidden>
<Icon name={display.icon} size={15} />
</span>
<span className="workspace-tabs-list__text">
<span className="workspace-tabs-list__title">{display.title}</span>
<span className="workspace-tabs-list__meta">{display.meta}</span>
</span>
</button>
<button
type="button"
className="workspace-tabs-list__close"
onClick={() => closeTab(display.id)}
title={t('common.close')}
aria-label={t('common.close')}
>
<Icon name="close" size={11} />
</button>
</div>
);
})
) : (
<div className="workspace-tabs-empty">No tabs found</div>
)}
</div>
</div>,
document.body,
)
: null}
<span className="workspace-tabs-list__icon" aria-hidden>
<Icon name={display.icon} size={15} />
</span>
<span className="workspace-tabs-list__text">
<span className={`workspace-tabs-list__title${display.tab.kind === 'project' ? ' workspace-tabs-list__title--project' : ''}`}>{display.title}</span>
<span className="workspace-tabs-list__meta">{display.meta}</span>
</span>
</button>
<button
type="button"
className="workspace-tabs-list__close"
onClick={() => closeTab(display.id)}
title={t('common.close')}
aria-label={t('common.close')}
>
<Icon name="close" size={11} />
</button>
</div>
);
})
) : (
<div className="workspace-tabs-empty">No tabs found</div>
)}
</div>
</div>,
document.body,
)
: null}
</div>
</div>
{hoverPreview && typeof document !== 'undefined' && !tabsMenuOpen
? createPortal(
Expand Down
28 changes: 28 additions & 0 deletions apps/web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,12 @@ code {
font-weight: 520;
letter-spacing: 0;
}
.workspace-tab__label--project {
/* Same display-layer capitalize as .app-project-title .title — see that rule for the
brand-name trade-off note. Scoped to project tabs only; Home/Marketplace/etc. use
localized strings that must not be auto-capitalized. */
text-transform: capitalize;
Comment on lines +688 to +691
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.

}
.workspace-tab__close {
width: 18px;
height: 18px;
Expand Down Expand Up @@ -854,6 +860,12 @@ code {
font-size: 12.5px;
font-weight: 600;
}
.workspace-tabs-list__title--project {
/* Same display-layer capitalize as .app-project-title .title — see that rule for the
brand-name trade-off note. Scoped to project tabs only; Home/Marketplace/etc. use
localized strings that must not be auto-capitalized. */
text-transform: capitalize;
}
.workspace-tabs-list__meta {
color: var(--text-muted);
font-size: 11px;
Expand Down Expand Up @@ -1215,6 +1227,12 @@ code {
text-overflow: ellipsis;
flex: 0 1 auto;
min-width: 0;
/* Display-layer fix: project names are stored as typed (often all-lowercase when
auto-generated), so capitalize the first letter of each word for readability.
Trade-off: brand names with intentional mid-word capitalisation (e.g. "iPhone")
will render incorrectly as "IPhone". A storage-layer fix (preserving input casing
on create/rename) would make this rule redundant and should remove it when landed. */
text-transform: capitalize;
}
.app-project-title .meta {
color: var(--text-muted);
Expand Down Expand Up @@ -8901,6 +8919,12 @@ button.connector-action.is-loading {
text-overflow: ellipsis;
color: var(--text-strong);
}
.design-card-name--project {
/* Same display-layer capitalize as .app-project-title .title — see that rule for the
brand-name trade-off note. Scoped to project cards only; live-artifact titles may
have intentional casing (brand names, version strings). */
text-transform: capitalize;
Comment on lines +8923 to +8926
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
Contributor 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.

Comment on lines +8923 to +8926
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
Contributor 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.

}
.design-card-meta {
font-size: 11.5px;
color: var(--text-muted);
Expand Down Expand Up @@ -9389,6 +9413,10 @@ button.connector-action.is-loading {
text-overflow: ellipsis;
/* Reserve room for the absolutely-positioned close button. */
padding-right: 20px;
/* Same display-layer capitalize as .app-project-title .title — see that rule for the
brand-name trade-off rationale. Selector is project-name-only (kanban renders only
project cards; live artifacts use .design-card-name in the grid view). */
text-transform: capitalize;
}
.design-kanban-card-meta {
font-size: 12px;
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/styles/home/recent-projects.css
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* Same display-layer capitalize as .app-project-title .title — see that rule for the
brand-name trade-off note. */
text-transform: capitalize;
}
.recent-projects__card-time {
font-size: 11.5px;
Expand Down
Loading
Loading