-
Notifications
You must be signed in to change notification settings - Fork 5.5k
fix(web): capitalize project name display across all five sites #2301
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e5369a0
6320ed4
9bf879d
96bc913
06918b5
16ac1e5
5103412
48c9dde
08902b7
b66b505
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
|
|
@@ -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}> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| <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( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These shared tab-label selectors also style non-project tabs. |
||
| } | ||
| .workspace-tab__close { | ||
| width: 18px; | ||
| height: 18px; | ||
|
|
@@ -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; | ||
|
|
@@ -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); | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This rule changes Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
|
@@ -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; | ||
|
|
||
There was a problem hiding this comment.
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
🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos..design-kanban-card-nameatDesignsTab.tsx:716-720, andapps/web/src/index.css:9063-9069still has notext-transformfor that selector. That means a project calledacme studiois 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.