Skip to content

Commit 2211b10

Browse files
LEANDERANTONYclaude
andcommitted
fix(workspace): port 2 A2 UX fixes — palette :active + locked-premium upgrade CTA
A2 cross-project audit: 12 remaining HelpmateAI Day-37 UI fixes visually + code diffed against Job Agent. 10 DISMISS (Job Agent's independently-built frontend already does the right thing — icon-only mobile search clustered with the avatar, fixed FAB assistant panel vs inline margin-top:auto ask-group, fully inline-styled FeedbackButtons, data-active accent palette rows, Step-7b tier-limit Notice + in-scope .b-notice-action CTA, expires_at retention labels, etc.). Only TWO real defects ported: 7913bb6 — command-palette :active touch press. CommandPalette sets data-active only via onMouseEnter / Arrow keys (no touch path), so a tapped row on a phone got zero press feedback. Added `.b-cmd-item :active` (+ icon) mirroring the existing `[data-active="true"]` treatment exactly, so touch and keyboard selection look identical. Pure additive CSS. 425a87d + 11a18d8 — locked Premium toggle had no actionable upgrade (P1). On Free the toggle was HTML-`disabled`, so its only upgrade hint was a hover `title` — invisible on touch, dead control. The toggle was already always-visible (11a18d8's "obvious/visible" half is satisfied), so only the actionable-locked-state needed porting. Split the disabled reasons: tier-locked stays interactive (a tap calls the new onPremiumLockedUpgrade -> WorkspaceShell surfaces the SAME upgrade Notice + .b-notice-action CTA the 429 tier-limit path uses, with workspaceQuota.upgrade_url); analysisLoading / out-of-credits remain real disables. Controlled `checked` stays false when tier-locked so premium never flips on. Reuses the established Notice mechanism — no new toast/CSS. tsc + eslint clean. Command palette regression-checked (renders fine, :active is additive). Premium-toggle path code-verified (reuses the already-rendered Notice+action surface); a live check would need a provisioned resume+JD workspace. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b82e772 commit 2211b10

3 files changed

Lines changed: 60 additions & 2 deletions

File tree

frontend/src/app/globals.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7444,6 +7444,23 @@ code {
74447444
border-color: rgba(90, 134, 255, 0.30);
74457445
color: var(--accent-strong);
74467446
}
7447+
/* Touch has no hover and no arrow keys, so the data-active highlight
7448+
can never MOVE on a phone — selectedIndex only changes via
7449+
onMouseEnter / ArrowUp-Down (see CommandPalette.tsx). Give every row
7450+
the same highlight the instant it's pressed: the touch analog of the
7451+
desktop cursor/hover. Pure CSS so it fires on touchstart with no JS
7452+
round-trip; declared after [data-active] (equal specificity) so a
7453+
press always reads, and it mirrors the [data-active] treatment so
7454+
touch and keyboard selection look identical. */
7455+
.b-cmd-item:active {
7456+
background: rgba(90, 134, 255, 0.10);
7457+
color: var(--fg);
7458+
}
7459+
.b-cmd-item:active .b-cmd-item-icon {
7460+
background: rgba(90, 134, 255, 0.16);
7461+
border-color: rgba(90, 134, 255, 0.30);
7462+
color: var(--accent-strong);
7463+
}
74477464
.b-cmd-item-main {
74487465
flex: 1;
74497466
min-width: 0;

frontend/src/components/workspace/AnalysisRunner.tsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ export type AnalysisRunnerProps = {
5353
* state because both this component and the run-hook need to read
5454
* it. */
5555
onPremiumChange: (next: boolean) => void;
56+
/** Called when a Free-tier user interacts with the tier-locked
57+
* premium toggle. The parent surfaces the standard upgrade Notice
58+
* (the same `.b-notice-action` CTA the 429 tier-limit path uses)
59+
* instead of the toggle being a dead HTML-`disabled` control whose
60+
* only upgrade hint is a hover `title` — which never shows on
61+
* touch (the P1 gap HelpmateAI's 425a87d/11a18d8 closed). */
62+
onPremiumLockedUpgrade: () => void;
5663
};
5764

5865
// Pipeline stages shown in the redesigned layout.
@@ -118,6 +125,7 @@ export function AnalysisRunner({
118125
quota,
119126
premium,
120127
onPremiumChange,
128+
onPremiumLockedUpgrade,
121129
}: AnalysisRunnerProps) {
122130
// Premium toggle gating logic.
123131
// * Disabled when quota is null (anonymous / restoring / fetch
@@ -137,6 +145,17 @@ export function AnalysisRunner({
137145
premiumAvailable && premiumLimit > 0 && premiumRemaining <= 0;
138146
const premiumDisabled =
139147
!premiumAvailable || analysisLoading || premiumOutOfCredits;
148+
// Split the "disabled" reasons. Tier-locked (Free) is NOT a hard
149+
// HTML-disable: a disabled checkbox is dead on touch and its only
150+
// upgrade hint is the hover `title`, which never shows on a phone —
151+
// so a Free user tapping Premium got zero feedback and no upgrade
152+
// path (the P1 gap HelpmateAI's 425a87d/11a18d8 closed). Keep it
153+
// interactive when tier-locked so a tap surfaces the upgrade CTA;
154+
// `premiumDisabled` still drives the LOCKED-LOOKING visual state.
155+
// analysisLoading / out-of-credits stay real disables — transient
156+
// or wait-for-reset, not an upgrade situation.
157+
const tierLocked = !premiumAvailable;
158+
const premiumInputDisabled = analysisLoading || premiumOutOfCredits;
140159
const premiumTooltip = !premiumAvailable
141160
? "Upgrade to Pro for premium AI (GPT-5.5)"
142161
: premiumOutOfCredits
@@ -238,9 +257,20 @@ export function AnalysisRunner({
238257
>
239258
<input
240259
aria-label="Run with premium AI (GPT-5.5)"
260+
aria-disabled={tierLocked || premiumInputDisabled}
241261
checked={premium && !premiumDisabled}
242-
disabled={premiumDisabled}
243-
onChange={(event) => onPremiumChange(event.target.checked)}
262+
disabled={premiumInputDisabled}
263+
onChange={(event) => {
264+
if (tierLocked) {
265+
// Locked by plan, not a transient state: don't flip
266+
// premium on — surface the upgrade CTA instead. The
267+
// controlled `checked` stays false on re-render
268+
// (premiumDisabled is true while tier-locked).
269+
onPremiumLockedUpgrade();
270+
return;
271+
}
272+
onPremiumChange(event.target.checked);
273+
}}
244274
type="checkbox"
245275
/>
246276
<span className="b-premium-toggle-label">

frontend/src/components/workspace/WorkspaceShell.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2097,6 +2097,17 @@ export function WorkspaceShell() {
20972097
currentWorkflowStage={currentWorkflowStage}
20982098
onClearRole={clearWorkspaceRole}
20992099
onPremiumChange={setPremium}
2100+
onPremiumLockedUpgrade={() =>
2101+
setWorkspaceNotice({
2102+
level: "info",
2103+
message:
2104+
"Premium AI (GPT-5.5) is a Pro feature. Upgrade your plan to run premium tailoring.",
2105+
action: {
2106+
label: "Upgrade",
2107+
href: workspaceQuota?.upgrade_url || "/#pricing",
2108+
},
2109+
})
2110+
}
21002111
onRunAnalysis={() => void handleRunAnalysis()}
21012112
premium={premium}
21022113
quota={workspaceQuota}

0 commit comments

Comments
 (0)