Skip to content
Closed
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
46 changes: 23 additions & 23 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,27 @@
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@tailwindcss/postcss": "^4.3.3",
"@tiptap/core": "^3.30.4",
"@tiptap/extension-code-block-lowlight": "^3.30.4",
"@tiptap/extension-details": "^3.30.4",
"@tiptap/extension-document": "^3.30.4",
"@tiptap/extension-highlight": "^3.30.4",
"@tiptap/extension-image": "^3.30.4",
"@tiptap/extension-link": "^3.30.4",
"@tiptap/extension-paragraph": "^3.30.4",
"@tiptap/extension-placeholder": "^3.30.4",
"@tiptap/extension-table": "^3.30.4",
"@tiptap/extension-table-cell": "^3.30.4",
"@tiptap/extension-table-header": "^3.30.4",
"@tiptap/extension-table-row": "^3.30.4",
"@tiptap/extension-task-item": "^3.30.4",
"@tiptap/extension-task-list": "^3.30.4",
"@tiptap/extension-text": "^3.30.4",
"@tiptap/extension-underline": "^3.30.4",
"@tiptap/pm": "^3.30.4",
"@tiptap/react": "^3.30.4",
"@tiptap/starter-kit": "^3.30.4",
"@tiptap/suggestion": "^3.30.4",
"@tiptap/core": "^3.30.5",
"@tiptap/extension-code-block-lowlight": "^3.30.5",
"@tiptap/extension-details": "^3.30.5",
"@tiptap/extension-document": "^3.30.5",
"@tiptap/extension-highlight": "^3.30.5",
"@tiptap/extension-image": "^3.30.5",
"@tiptap/extension-link": "^3.30.5",
"@tiptap/extension-paragraph": "^3.30.5",
"@tiptap/extension-placeholder": "^3.30.5",
"@tiptap/extension-table": "^3.30.5",
"@tiptap/extension-table-cell": "^3.30.5",
"@tiptap/extension-table-header": "^3.30.5",
"@tiptap/extension-table-row": "^3.30.5",
"@tiptap/extension-task-item": "^3.30.5",
"@tiptap/extension-task-list": "^3.30.5",
"@tiptap/extension-text": "^3.30.5",
"@tiptap/extension-underline": "^3.30.5",
"@tiptap/pm": "^3.30.5",
"@tiptap/react": "^3.30.5",
"@tiptap/starter-kit": "^3.30.5",
"@tiptap/suggestion": "^3.30.5",
"@types/react-resizable": "^4.0.0",
"@types/turndown": "^5.0.6",
"d3-drag": "^3.0.0",
Expand All @@ -47,7 +47,7 @@
"dompurify": "^3.4.13",
"lowlight": "^3.3.0",
"lucide-react": "^1.31.0",
"next": "16.3.1",
"next": "16.3.3",
"postcss": "^8.5.26",
"react": "^19.2.8",
"react-dom": "^19.2.8",
Expand Down Expand Up @@ -75,7 +75,7 @@
"@types/react-grid-layout": "^1.3.6",
"@types/simple-peer": "^9.11.9",
"eslint": "^9.39.5",
"eslint-config-next": "16.3.1",
"eslint-config-next": "16.3.3",
"typescript": "^6.0.3"
}
}
106 changes: 53 additions & 53 deletions apps/web/src/app/(app)/settings/ai/page.tsx

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apps/web/src/app/(app)/settings/api-access/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export default function ApiAccessPage() {
return (
<div className="h-full overflow-y-auto">
<div className="p-6 max-w-[820px]">
<div className="flex items-center justify-between mb-4">
<div className="mb-4 flex flex-col items-start justify-between gap-3 sm:flex-row sm:items-center">
<div>
<h2
className="text-[18px] font-semibold"
Expand All @@ -153,7 +153,7 @@ export default function ApiAccessPage() {
</div>
<button
onClick={() => setShowCreate(!showCreate)}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[12px] font-medium"
className="flex min-h-10 shrink-0 items-center gap-1.5 whitespace-nowrap px-3 py-1.5 rounded-lg text-[12px] font-medium"
style={{
background: 'var(--accent)',
color: 'white',
Expand Down
92 changes: 46 additions & 46 deletions apps/web/src/app/(app)/settings/calendar/page.tsx

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion apps/web/src/app/(app)/settings/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import styles from './settings.module.css';

export const metadata = { title: 'Settings' };

export default function SettingsLayout({ children }: { children: React.ReactNode }) {
return children;
return <div className={styles.settings}>{children}</div>;
}
1 change: 0 additions & 1 deletion apps/web/src/app/(app)/settings/library/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,6 @@ export default function LibraryPage() {
<PageHeader
title="Task templates"
description="Turn repeatable work into ready-made task sets that can be applied to any project."
compact
/>

{/* Skills tab */}
Expand Down
15 changes: 7 additions & 8 deletions apps/web/src/app/(app)/settings/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -251,13 +251,12 @@ export default function ProfileSettingsPage() {
return (
<div className="h-full overflow-y-auto">
<div className="mx-auto max-w-4xl px-4 py-6 md:px-6 md:py-8">
<div className="mb-6 flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
<div className="mb-6 flex flex-col gap-2 border-b pb-6 md:flex-row md:items-end md:justify-between" style={{ borderColor: 'var(--border)' }}>
<div>
<p className="eyebrow mb-2" style={{ fontFamily: 'var(--font-heading)' }}>Account</p>
<h1 className="text-[28px] font-semibold leading-tight" style={{ color: 'var(--foreground)', fontFamily: 'var(--font-heading)' }}>
My profile
<h1 className="text-2xl font-semibold tracking-tight" style={{ color: 'var(--foreground)', fontFamily: 'var(--font-heading)' }}>
Profile
</h1>
<p className="mt-2 max-w-2xl text-[14px] leading-6" style={{ color: 'var(--muted)' }}>
<p className="mt-2 max-w-2xl text-[14px] leading-6" style={{ color: 'var(--foreground-secondary)' }}>
Shape how teammates understand your role, availability, and collaboration preferences.
</p>
</div>
Expand All @@ -270,7 +269,7 @@ export default function ProfileSettingsPage() {
onChange={setSection}
/>
<div hidden={section === 'security'}>
<form onSubmit={handleSave} className="grid min-w-0 gap-5 lg:grid-cols-[minmax(0,1fr)_320px]">
<form onSubmit={handleSave} className="grid min-w-0 gap-5 xl:grid-cols-[minmax(0,1fr)_240px]">
<section className="min-w-0 space-y-5">
<div hidden={section !== 'identity'} className="min-w-0 rounded-xl p-5" style={{ background: 'var(--card-bg)', border: '1px solid var(--border)' }}>
<h2 className="mb-4 text-[15px] font-semibold" style={{ color: 'var(--foreground)', fontFamily: 'var(--font-heading)' }}>
Expand Down Expand Up @@ -455,10 +454,10 @@ export default function ProfileSettingsPage() {
</div>
</aside>

<div className="lg:col-span-2">
<div className="border-t pt-5 xl:col-span-2" style={{ borderColor: 'var(--border)' }}>
{error && <div className="mb-3 text-[13px]" style={{ color: 'var(--status-red)' }}>{error}</div>}
{saveMessage && <div className="mb-3 text-[13px]" style={{ color: 'var(--status-green)' }}>{saveMessage}</div>}
<button type="submit" disabled={saving || !name.trim()} className="inline-flex items-center gap-2 rounded-lg px-4 py-2 text-[13px] font-semibold disabled:opacity-50" style={{ background: 'var(--accent)', color: 'white' }}>
<button type="submit" disabled={saving || !name.trim()} className="inline-flex min-h-10 items-center gap-2 rounded-lg px-4 py-2 text-[13px] font-semibold disabled:opacity-50" style={{ background: 'var(--settings-action)', color: 'white' }}>
{saving ? <Loader2 size={15} className="animate-spin" /> : <Save size={15} />}
Save profile & preferences
</button>
Expand Down
47 changes: 47 additions & 0 deletions apps/web/src/app/(app)/settings/settings.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.settings {
display: contents;
--border: color-mix(in srgb, var(--text-primary) 17%, transparent);
--border-default: var(--border);
--border-strong: color-mix(in srgb, var(--text-primary) 28%, transparent);
--settings-action: #7056dc;
}

.settings :is(button, a, input, textarea, select, summary):focus-visible {
outline: 2px solid var(--primary);
outline-offset: 3px;
}

.settings :is(input, textarea, select) {
max-width: 100%;
accent-color: var(--accent);
}

.settings :is(input:not([type='checkbox']):not([type='radio']):not([type='file']), select) {
min-height: 2.5rem;
}

.settings textarea {
line-height: 1.6;
}

.settings :is(button, summary) {
transition: box-shadow 140ms ease, background-color 140ms ease;
}

.settings button:not(:disabled):hover {
box-shadow: inset 0 0 0 100px color-mix(in srgb, var(--text-primary) 5%, transparent);
}

.settings button:disabled {
cursor: not-allowed;
}

.settings :is(h1, h2, h3) {
text-wrap: balance;
}

@media (prefers-reduced-motion: reduce) {
.settings :is(button, summary) {
transition: none;
}
}
11 changes: 6 additions & 5 deletions apps/web/src/app/(app)/settings/tags/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,22 +156,23 @@ export default function TagsPage() {
{/* Create tag */}
{creating && (
<div className="mb-6 p-4 rounded-xl" style={{ background: 'var(--surface-container)', border: '1px solid var(--border)' }}>
<div className="flex items-center gap-3 mb-3">
<div className="w-5 h-5 rounded-full flex-shrink-0" style={{ background: newColor }} />
<div className="mb-3 grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 sm:flex">
<div className="hidden w-5 h-5 rounded-full flex-shrink-0 sm:block" style={{ background: newColor }} />
<input autoFocus value={newName}
onChange={e => setNewName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleCreate(); if (e.key === 'Escape') setCreating(false); }}
placeholder="Tag name (e.g. launch, q3-planning)"
className="flex-1 h-9 px-3 rounded-lg text-[13px] outline-none"
aria-label="Tag name"
className="col-span-2 min-w-0 w-full flex-1 h-9 px-3 rounded-lg text-[13px] outline-none"
style={{ background: 'var(--input-bg)', color: 'var(--foreground)', border: '1px solid var(--input-border)' }} />
<button onClick={handleCreate} className="px-4 h-9 rounded-lg text-[13px] font-medium text-white"
style={{ background: 'var(--accent)' }}>Create</button>
<button onClick={() => setCreating(false)} className="p-2 rounded-lg" style={{ color: 'var(--muted)' }}>
<button aria-label="Cancel tag creation" onClick={() => setCreating(false)} className="p-2 rounded-lg" style={{ color: 'var(--muted)' }}>
<X size={16} /></button>
</div>
<div className="flex gap-1.5 flex-wrap">
{TAG_COLORS.map(c => (
<button key={c} onClick={() => setNewColor(c)}
<button key={c} aria-label={`Choose tag color ${c}`} aria-pressed={newColor === c} onClick={() => setNewColor(c)}
className="w-6 h-6 rounded-full transition-all"
style={{ background: c, boxShadow: newColor === c ? `0 0 0 2px var(--background), 0 0 0 4px ${c}` : 'none' }} />
))}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/app/(app)/settings/workflows/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export default function TaskRulesPage() {
return (
<div className="h-full overflow-y-auto">
<div className="max-w-[820px] mx-auto px-6 py-8">
<div className="flex items-center justify-between mb-6">
<div className="mb-6 flex flex-col items-start justify-between gap-3 sm:flex-row sm:items-center">
<div>
<h1 className="section-title" style={{ fontFamily: 'var(--font-heading)' }}>
Task rules
Expand All @@ -151,7 +151,7 @@ export default function TaskRulesPage() {
</div>
{!creating && (
<button onClick={() => setCreating(true)}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[13px] font-medium text-white"
className="flex min-h-10 shrink-0 items-center gap-1.5 whitespace-nowrap px-3 py-1.5 rounded-lg text-[13px] font-medium text-white"
style={{ background: 'var(--accent)' }}>
<Plus size={14} /> New task rule
</button>
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/components/settings-section-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ export function SettingsSectionNav<T extends string>({
onChange: (value: T) => void;
}) {
return (
<nav aria-label={label} className="mb-6 flex flex-wrap gap-2 border-b pb-3" style={{ borderColor: 'var(--border)' }}>
<nav aria-label={label} className="mb-6 flex flex-wrap gap-1 rounded-xl border p-1" style={{ borderColor: 'var(--border)', background: 'var(--surface-container-low)' }}>
{sections.map((section) => (
<button
key={section.id}
type="button"
aria-pressed={value === section.id}
onClick={() => onChange(section.id)}
className="rounded-lg px-3 py-2 text-[13px] font-medium focus-visible:outline-2 focus-visible:outline-offset-2"
className="min-h-10 rounded-lg px-3 py-2 text-[13px] font-medium focus-visible:outline-2 focus-visible:outline-offset-2"
style={{
background: value === section.id ? 'var(--accent-subtle)' : 'transparent',
color: value === section.id ? 'var(--accent)' : 'var(--foreground-secondary)',
color: value === section.id ? 'var(--primary)' : 'var(--foreground-secondary)',
}}
>
{section.label}
Expand Down
83 changes: 83 additions & 0 deletions docs/superpowers/audits/2026-09-09-settings-review-passes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Settings review passes — 2026-09-09

Scope: visual and interaction review of the current Settings implementation on
`codex/settings-visual-polish`, using the localhost:3025 app and synthetic,
read-only API fixture. This is not a production integration certification.

## Pass 1: route inventory and desktop layout

Visited all 18 top-level Settings routes at 1440 CSS px. Pages loaded without a
rendered application crash or page-level horizontal overflow. Profile's initial
automated heading observation matched an Overview card during navigation;
the saved Profile screenshot and subsequent direct inspection verified the page.

| Route | Desktop | Mobile initial layout (390 CSS px) |
| --- | --- | --- |
| `/settings` | Reviewed | Reviewed |
| `/settings/profile` | Reviewed | Reviewed |
| `/settings/mcp-access` | Reviewed | Reviewed |
| `/settings/calendar` | Reviewed | Reviewed |
| `/settings/members` | Reviewed | Reviewed |
| `/settings/teams` | Reviewed | Reviewed |
| `/settings/apps` | Reviewed | Reviewed |
| `/settings/modules` | Reviewed | Reviewed |
| `/settings/groups` | Reviewed | Reviewed |
| `/settings/agent-employees` | Reviewed | Reviewed |
| `/settings/ai` | Reviewed | Reviewed |
| `/settings/integrations` | Reviewed | Reviewed |
| `/settings/agent` | Reviewed | Reviewed |
| `/settings/library` | Reviewed | Missing title fixed |
| `/settings/workflows` | Reviewed | Narrow action fixed |
| `/settings/tags` | Reviewed | Initial layout fits; form overflow fixed |
| `/settings/projects` | Reviewed | Reviewed |
| `/settings/api-access` | Reviewed | Narrow action fixed |

## Pass 2: mobile screenshots

Saved and inspected initial screenshots for all 18 routes. Page-level width alone
was insufficient: a nested horizontal scrolling container hid overflowing tag-form
controls. The follow-up check measured individual visible control bounds too.

Evidence: `tmp/preview-evidence/audit-mobile-0.png` through `audit-mobile-17.png`,
three `audit-sheet-*.png` contact sheets, and `settings-audit.json`. These ignored
local artifacts use sample data; they are not checked into the repository.

## Pass 3: forms, secondary routes, and fixes

Opened service-key, task-rule, tag, group, team and invitation forms at phone width.
Inspected agent creation and the staged Contacts App detail route. No invitations,
credentials, App activation, trust changes or external writes were performed.

Reproduced and fixed:

1. Task templates used a compact PageHeader without an alternative mobile title.
Removed compact mode; the title is now visibly rendered at 390px.
2. Task-rule creation action was squeezed into a tall, narrow label by the heading
row. Stack the header on narrow screens and keep the action from shrinking.
3. Service API creation action had the same problem. Applied the same bounded fix.
4. Tag creation's Create and Cancel controls extended to approximately 397px and
441px on a 390px viewport. Use a two-row mobile grid and a shrinkable input.
Give the input, cancel control and colour buttons accessible names and expose
the selected colour with aria-pressed.

Fresh recheck: zero offscreen tag controls at 390px and 320px; colour selection
updates aria-pressed; cancel closes the form. Inspected corrected screenshots for
all four pages: `audit-fixed-{tags,templates,rules,service}-mobile.png`.

Web typecheck, focused ESLint for the four changed pages, three Settings navigation
tests, and git diff whitespace checks passed. No backend contracts changed.

## Remaining evidence gaps

- The four employee record routes (detail, developer, heartbeats, webhooks) need
populated employee records; this fixture has none. Their healthy rendered states
were not certified in this pass.
- Empty lists do not prove populated tables, long records, pagination or every
dialog state. The staged App detail is one sample, not lifecycle certification.
- Save persistence, real OAuth/MCP connections, role-based runtime authorization,
screen-reader use, browser zoom and exhaustive light-theme states remain outside
this pass. Previous light-theme inspection was representative, not exhaustive.
- Some existing forms still use placeholder-only labels and small controls. The
fixes above are specific findings, not a claim of complete accessibility.

PR #325 remains stacked on #324 and unmerged for user review.
39 changes: 39 additions & 0 deletions docs/superpowers/plans/2026-09-09-settings-maturity-program.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,42 @@ production deployment, real credential grants or external messages are implied.
`tmp/preview-evidence/polish-*.png`. This is fixture-based visual verification.
- The Settings implementation schedule was deleted at the user's request. Continue
only on direct user instruction; do not recreate it from this checkpoint.
- Other Settings visual pass (`codex/settings-visual-polish`, stacked on #324):
shared Settings-only border tokens, field sizing, keyboard focus, hover and
reduced-motion styles; stronger section navigation on Profile/Calendar/AI.
Profile has a consistent title, wider form and narrower preview, and clearer
save action. Calendar/AI use aligned content widths, larger body text and
consistent headings. No API, authorization or persistence changes.
Verified Profile draft retention across all three sections; Calendar section
switching and mobile add form; all four AI sections at 390px. Inspected desktop
Profile/Calendar/AI and mobile Profile/Calendar/AI/Apps. Desktop route inventory
covered People, Teams, Apps, Modules, groups, tool connections, governance,
templates, rules, tags and recovery without page-level overflow. This is a
shared visual baseline, not a completed individual redesign of every route.
Fixture evidence: `tmp/preview-evidence/settings-*.png`. Persistence and full
mobile/role coverage of the administration pages remain unverified.
- Three review passes completed over 18 top-level routes (desktop, mobile, forms).
Fixed missing mobile Task templates title, squeezed task-rule/service-key actions,
and tag-form overflow with accessible colour/cancel controls. Fresh 320/390px
rechecks, typecheck, focused lint and navigation tests passed. See
`docs/superpowers/audits/2026-09-09-settings-review-passes.md` for exact coverage
and gaps, including the four employee detail routes without fixture records.

## Merge and demo closeout — 2026-09-09

The user approved merging the Settings stack and updating demo.deft.ing. PR #323
is the consolidated candidate incorporating #324 and #325. Keep the schedule
deleted. Remaining Track B/C/D and Gate G work is outside this UI closeout.

The final dependency audit reproduced eight advisories. Updated Next.js and its
ESLint config to 16.3.3, the Tiptap family to a patched compatible release, and
Hono, Sharp and js-yaml overrides. The regenerated lockfile passes the full
low-threshold pnpm audit with no known vulnerabilities. Full candidate CI remains
the merge gate; no bypass of image scanning or database-backed browser smoke.

The public demo routes to the RC1 UI candidate in b3appsprivate20260906; the older
preview.14 container remains separate. Deploy the merged revision through a
separate source checkout, preserve site configuration and existing feature flags,
record the old image, take a stopped database/uploads/configuration backup, and
rehearse the versioned upgrade against a restored database before live cutover.
Verify doctor, connector smoke, build identity and rendered Settings after deploy.
Loading