Skip to content
Merged
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
11 changes: 8 additions & 3 deletions apps/api-runner/node_modules/.bin/tsc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions apps/api-runner/node_modules/.bin/tsserver

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions apps/backend/app/api/routes/auth/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ async def update_profile(
if payload.personal_info is not None:
updates["personal_info"] = payload.personal_info.model_dump()

if payload.persona is not None:
updates["persona"] = payload.persona

if updates:
await update_user_profile(current_user.uid, updates)

Expand Down
3 changes: 3 additions & 0 deletions apps/backend/app/api/routes/auth/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ class UserProfileResponse(BaseModel):
certifications: list[Certification] = Field(default_factory=list)
portfolio_settings: PortfolioSettings | None = None
personal_info: PersonalInfo | None = None
# Role picked during onboarding (frontend/backend/fullstack/devops/qa/designer/exploring)
persona: str | None = None
onboarding_completed: bool = False
# Workspace migration fields (T25)
workspace_setup_at: int | None = None
Expand All @@ -128,6 +130,7 @@ class UpdateProfileRequest(BaseModel):
certifications: list[Certification] | None = None
portfolio_settings: PortfolioSettings | None = None
personal_info: PersonalInfo | None = None
persona: str | None = None


class OkResponse(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ const DashboardPage: React.FC = () => {
mobileOnly
/>

<div className="px-3 md:px-8 pb-24 md:pb-12">
<div className="px-3 pt-4 md:px-8 md:pt-8 pb-24 md:pb-12">
<div className="max-w-7xl mx-auto space-y-5 md:space-y-8">
{/* ── Desktop Hero (inside padded container for alignment) ── */}
<RevealItem index={0}>
Expand Down
79 changes: 46 additions & 33 deletions apps/web/src/components/global-command-palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,31 @@ export function GlobalCommandPalette() {
)

const commandSurfaceClass =
'[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5'
'[&_[cmdk-group-heading]]:px-3 [&_[cmdk-group-heading]]:pt-2.5 [&_[cmdk-group-heading]]:pb-1 [&_[cmdk-group-heading]]:text-[10px] [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-[0.14em] [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-14 [&_[cmdk-input]]:text-base'

// Shared row treatment — gradient selection sweep + icon chip (Raycast-style).
const itemClass =
'group mx-1 flex items-center gap-3 rounded-xl px-2 py-2 transition-colors aria-selected:bg-gradient-to-r aria-selected:from-primary/[0.14] aria-selected:via-primary/[0.06] aria-selected:to-transparent aria-selected:ring-1 aria-selected:ring-inset aria-selected:ring-primary/20'
const chipClass =
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-muted/60 text-muted-foreground ring-1 ring-inset ring-border/50 transition-colors group-aria-selected:bg-gradient-to-br group-aria-selected:from-primary/20 group-aria-selected:to-violet-500/10 group-aria-selected:text-primary group-aria-selected:ring-primary/20'

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent
showCloseButton={false}
className={cn(
'overflow-hidden p-0 gap-0 max-w-2xl',
'max-h-[min(85vh,640px)] flex flex-col'
'max-h-[min(85vh,640px)] flex flex-col',
'rounded-2xl border-border/60 bg-popover/90 backdrop-blur-2xl',
'shadow-2xl shadow-primary/10 ring-1 ring-inset ring-white/[0.04]'
)}
>
<DialogTitle className="sr-only">Search tools and pages</DialogTitle>
{/* Gradient hairline — the palette's signature accent */}
<div
aria-hidden
className="h-px w-full shrink-0 bg-gradient-to-r from-transparent via-primary/50 to-transparent"
/>
<Command
className={cn(
'rounded-none border-none bg-transparent shadow-none',
Expand All @@ -177,7 +190,7 @@ export function GlobalCommandPalette() {
<CommandInput
placeholder="Search tools and pages…"
aria-label="Search tools and pages"
className="focus-visible:outline-none"
className="focus-visible:outline-none placeholder:text-muted-foreground/60"
/>
<CommandList className="max-h-[min(60vh,480px)] overflow-y-auto">
<CommandEmpty>No results found.</CommandEmpty>
Expand All @@ -191,15 +204,17 @@ export function GlobalCommandPalette() {
key={`recent-${entry.url}`}
value={`recent ${entry.searchValue} ${entry.url}`.toLowerCase()}
onSelect={() => run(entry)}
className="mx-1 flex items-start gap-3 rounded-lg py-2.5 transition-colors aria-selected:bg-gradient-to-r aria-selected:from-primary/[0.12] aria-selected:to-primary/[0.04] aria-selected:ring-1 aria-selected:ring-inset aria-selected:ring-primary/15"
className={itemClass}
>
<ItemIcon className="mt-0.5 h-4 w-4 shrink-0 opacity-70" aria-hidden />
<span className={chipClass}>
<ItemIcon className="h-4 w-4" aria-hidden />
</span>
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
<span className="truncate font-medium leading-none">
{entry.title}
</span>
{entry.description ? (
<span className="line-clamp-2 text-xs text-muted-foreground">
<span className="line-clamp-1 text-xs text-muted-foreground">
{entry.description}
</span>
) : null}
Expand All @@ -221,24 +236,23 @@ export function GlobalCommandPalette() {
key={`pinned-${entry.url}`}
value={`pinned ${entry.searchValue} ${entry.url}`.toLowerCase()}
onSelect={() => run(entry)}
className="mx-1 flex items-start gap-3 rounded-lg py-2.5 transition-colors aria-selected:bg-gradient-to-r aria-selected:from-primary/[0.12] aria-selected:to-primary/[0.04] aria-selected:ring-1 aria-selected:ring-inset aria-selected:ring-primary/15"
className={itemClass}
>
<Star
className="mt-0.5 h-4 w-4 shrink-0 fill-primary text-primary"
aria-hidden
/>
<span className={chipClass}>
<ItemIcon className="h-4 w-4" aria-hidden />
</span>
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
<span className="truncate font-medium leading-none">
{entry.title}
</span>
{entry.description ? (
<span className="line-clamp-2 text-xs text-muted-foreground">
<span className="line-clamp-1 text-xs text-muted-foreground">
{entry.description}
</span>
) : null}
</span>
<ItemIcon
className="mt-0.5 h-3.5 w-3.5 shrink-0 opacity-40"
<Star
className="h-3.5 w-3.5 shrink-0 fill-primary text-primary"
aria-hidden
/>
</CommandItem>
Expand All @@ -259,18 +273,17 @@ export function GlobalCommandPalette() {
key={entry.url}
value={`${entry.searchValue} ${entry.url}`.toLowerCase()}
onSelect={() => run(entry)}
className="flex items-start gap-3 py-2.5 aria-selected:bg-accent"
className={itemClass}
>
<ItemIcon
className="mt-0.5 h-4 w-4 shrink-0 opacity-70"
aria-hidden
/>
<span className={chipClass}>
<ItemIcon className="h-4 w-4" aria-hidden />
</span>
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
<span className="truncate font-medium leading-none">
{entry.title}
</span>
{entry.description ? (
<span className="line-clamp-2 text-xs text-muted-foreground">
<span className="line-clamp-1 text-xs text-muted-foreground">
{entry.description}
</span>
) : null}
Expand All @@ -282,29 +295,29 @@ export function GlobalCommandPalette() {
</React.Fragment>
))}
</CommandList>
<div className="flex flex-wrap items-center gap-x-4 gap-y-1.5 border-t border-border/60 px-4 py-3 text-[11px] text-muted-foreground">
<span>
<kbd className="pointer-events-none rounded border border-border bg-muted/80 px-1.5 py-0.5 font-mono">
<div className="flex flex-wrap items-center gap-x-4 gap-y-1.5 border-t border-border/50 bg-muted/20 px-4 py-2.5 text-[11px] text-muted-foreground">
<span className="inline-flex items-center gap-1.5">
<kbd className="pointer-events-none rounded-md border border-border/60 bg-background/80 px-1.5 py-0.5 font-mono text-[10px] font-medium text-foreground/70 shadow-sm">
↑↓
</kbd>{' '}
</kbd>
navigate
</span>
<span>
<kbd className="pointer-events-none rounded border border-border bg-muted/80 px-1.5 py-0.5 font-mono">
<span className="inline-flex items-center gap-1.5">
<kbd className="pointer-events-none rounded-md border border-border/60 bg-background/80 px-1.5 py-0.5 font-mono text-[10px] font-medium text-foreground/70 shadow-sm">
</kbd>{' '}
</kbd>
open
</span>
<span>
<kbd className="pointer-events-none rounded border border-border bg-muted/80 px-1.5 py-0.5 font-mono">
<span className="inline-flex items-center gap-1.5">
<kbd className="pointer-events-none rounded-md border border-border/60 bg-background/80 px-1.5 py-0.5 font-mono text-[10px] font-medium text-foreground/70 shadow-sm">
esc
</kbd>{' '}
</kbd>
close
</span>
<span className="ml-auto">
<kbd className="pointer-events-none rounded border border-border bg-muted/80 px-1.5 py-0.5 font-mono">
<span className="ml-auto inline-flex items-center gap-1.5">
<kbd className="pointer-events-none rounded-md border border-border/60 bg-background/80 px-1.5 py-0.5 font-mono text-[10px] font-medium text-foreground/70 shadow-sm">
{modLabel}K
</kbd>{' '}
</kbd>
toggle
</span>
</div>
Expand Down
17 changes: 11 additions & 6 deletions apps/web/src/components/nav-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { ModeToggle } from "@/components/modeToggle";
import { getToolMessageKey } from "@/lib/tool-i18n";
import { routeConfig } from "@/lib/route-config";
import { WorkspaceSwitcherDropdown } from "@/components/workspace-switcher-dropdown";
import { OrgSwitcherDropdown } from "@/components/org-switcher-dropdown";
import { WorkspaceQuickActions } from "@/components/workspace-quick-actions";
import { WorkspaceBreadcrumb } from "@/components/workspace-breadcrumb";
import { NotificationsBell } from "@/components/notifications-bell";

function openCommandPalette() {
Expand All @@ -27,7 +27,7 @@ function CommandTrigger() {
>
<Search className="h-4 w-4 shrink-0" />
<span className="flex-1 truncate text-left">Search or jump to…</span>
<kbd className="inline-flex items-center gap-0.5 rounded border border-border/60 bg-background/70 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground">
<kbd className="inline-flex items-center gap-0.5 rounded-md border border-border bg-background px-1.5 py-0.5 font-mono text-[11px] font-semibold text-foreground/80 shadow-sm">
⌘K
</kbd>
</button>
Expand Down Expand Up @@ -63,19 +63,23 @@ export function NavBar() {
const config = match?.[1];
const matchedRoute = match?.[0];

if (!config) {
// The dashboard needs no page title — the sidebar highlights it and the page
// greets the user. A "Dashboard" label beside the org switcher is noise.
if (!config || matchedRoute === '/dashboard') {
// Minimal bar everywhere with a sidebar so chrome (workspace, bell, theme)
// is reachable on /dashboard, /settings, etc. — not just /app routes.
return (
<header className="sticky top-0 z-20 hidden w-full border-b border-border/60 bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/60 md:block">
<div className="flex h-14 items-center justify-end gap-2 px-4">
<div className="flex h-14 items-center gap-2 px-4">
<div className="flex min-w-0 flex-1 items-center">
<OrgSwitcherDropdown />
</div>
<CommandTrigger />
<WorkspaceSwitcherDropdown />
<WorkspaceQuickActions />
<NotificationsBell />
<ModeToggle />
</div>
<WorkspaceBreadcrumb />
</header>
);
}
Expand All @@ -94,6 +98,8 @@ export function NavBar() {
<header className="sticky top-0 z-20 hidden w-full border-b border-border/60 bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/60 md:block">
<div className="flex h-14 w-full items-center gap-4 px-4">
<div className="flex min-w-0 flex-1 items-center gap-2.5">
<OrgSwitcherDropdown />
<div className="h-5 w-px shrink-0 bg-border/60" aria-hidden />
<div className="shrink-0 rounded-lg bg-gradient-to-br from-primary/15 to-violet-500/10 p-1.5 ring-1 ring-inset ring-border/50">
<Icon className="h-4 w-4 text-primary" strokeWidth={2} />
</div>
Expand All @@ -116,7 +122,6 @@ export function NavBar() {
<ModeToggle />
</div>
</div>
<WorkspaceBreadcrumb />
</header>
);
}
Loading