Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c0cfb47
feat(web): scripted full-screen product demo mode
vibegui Jun 15, 2026
8d1ef61
feat(web): pace the demos and end with a CTA card
vibegui Jun 15, 2026
fa249db
feat(web): inline sprint plan + PR, agent sidebar, daily digest
vibegui Jun 16, 2026
f32eca5
feat(demo): replace the two demos with one 'turtles' scenario — bread…
vibegui Jul 5, 2026
d44568d
fix(demo): use the real Studio UX — shell, hero, composer, tiles; Sha…
vibegui Jul 5, 2026
a33c42d
feat(demo): org rail as mini-dashboards; org agents in home sidebar
vibegui Jul 5, 2026
88296a0
feat(demo): give the org rail equal billing — 50/50 with the chat
vibegui Jul 5, 2026
9b8fb43
feat(demo): threads everywhere + the brief ends in a navigable action
vibegui Jul 5, 2026
f54e491
polish(demo): narrator pill purple/top/blurred; CTA card as app UI wi…
vibegui Jul 5, 2026
eb84956
fix(demo): the pilot finds and delegates the fix itself; agent zoom b…
vibegui Jul 5, 2026
8329f07
feat(demo): sender identities, Settings-as-agent with MCP-app preview…
vibegui Jul 5, 2026
0c9c4a0
feat(demo): per-agent MCP apps (ops dashboard, sales, kanban), invite…
vibegui Jul 5, 2026
645cdcc
chore(demo): regenerate narration clips with Zoe (Premium)
vibegui Jul 5, 2026
af76ff3
feat(demo): director's-cut narration — full script, Zoe @186wpm, on b…
vibegui Jul 5, 2026
c9be5e2
feat(demo): start card (audio unlock), Esc to pause, Zoe @176wpm
vibegui Jul 5, 2026
8d543e2
fix(demo): rewrite line 4; every narration clip now finishes before i…
vibegui Jul 5, 2026
69166e4
feat(demo): narration voice → Isha (Premium) @168wpm; re-time holds t…
vibegui Jul 5, 2026
51365aa
feat(demo): no-crop narration guarantee + 'org' purged from all copy
vibegui Jul 5, 2026
4871a6a
chore(demo): narration to Isha @170wpm; VO_MS from real measurements
vibegui Jul 5, 2026
8461113
chore(demo): opening line names the product — "your deco Studio"
vibegui Jul 5, 2026
d14185a
feat(demo): subtitle cadence, agent-loop SLO card, sharper script @18…
vibegui Jul 5, 2026
1a04f5e
feat(demo): narration via Maya1 TTS (open source, Indian-English)
vibegui Jul 5, 2026
5ad9a80
chore(demo): script tightening — 16 lines, fresh takes
vibegui Jul 5, 2026
bf12946
fix(demo): no blank chat on team landing; share popover holds longer
vibegui Jul 5, 2026
0aaa14c
feat(demo): narration voice → Maya1 US female; script down to 15 lines
vibegui Jul 6, 2026
c727f1f
feat(demo): MCP-app switcher in preview chrome; on-brand kanban; 40/60
vibegui Jul 6, 2026
e76b980
fix(demo): minmax(0,·) grid tracks so thinking text can't widen chat
vibegui Jul 6, 2026
b2f7740
feat(demo): breadcrumb-context spotlight + full share arc; Studio kanban
vibegui Jul 6, 2026
859910f
chore(demo): line 17 flows from the logo line — "You can also connect…"
vibegui Jul 6, 2026
286801f
fix(demo): satisfy evolved ChatStreamContextValue after rebase
vibegui Jul 11, 2026
3194268
feat(shell): org selector moves up into a toolbar breadcrumb
vibegui Jul 11, 2026
6b3ee9b
feat(home): / becomes MY deco — cross-org aggregate of your threads
vibegui Jul 11, 2026
8c41cca
feat(home): content-grounded AI analysis on MY deco cards
vibegui Jul 11, 2026
4a0cfe3
feat(home): empty-state composer — start a thread in any org from MY …
vibegui Jul 11, 2026
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
Binary file added apps/mesh/public/demo/vo/vo-01.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-02.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-03.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-04.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-05.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-06.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-07.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-08.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-09.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-10.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-11.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-12.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-13.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-14.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-15.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-16.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-17.m4a
Binary file not shown.
Binary file added apps/mesh/public/demo/vo/vo-18.m4a
Binary file not shown.
160 changes: 5 additions & 155 deletions apps/mesh/src/web/components/account-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,78 +20,34 @@ import {
import { cn } from "@deco/ui/lib/utils.ts";
import { useIsMobile } from "@deco/ui/hooks/use-mobile.ts";
import {
Check,
Copy01,
Download01,
File06,
Globe01,
LogOut01,
Monitor01,
Moon01,
Plus,
SearchMd,
Settings02,
Shield01,
Sun,
Users03,
VolumeMax,
VolumeX,
XClose,
} from "@untitledui/icons";
import { GitHubIcon } from "@daveyplate/better-auth-ui";
import { SidebarMenuButton } from "@deco/ui/components/sidebar.tsx";
import { authClient, useActiveOrganizations } from "@/web/lib/auth-client";
import { authClient } from "@/web/lib/auth-client";
import { useProjectContext } from "@decocms/mesh-sdk";
import { track } from "@/web/lib/posthog-client";
import { clearPersistedQueryCache } from "@/web/lib/query-persist";
import { CreateOrganizationDialog } from "@/web/components/create-organization-dialog";
import {
getOrgColorStyle,
OrganizationsPanel,
} from "@/web/components/header/org-switcher";
import { usePreferences, type ThemeMode } from "@/web/hooks/use-preferences.ts";
import { toast } from "@deco/ui/components/sonner.js";

function getOrgColorStyle(name: string): {
backgroundColor: string;
color: string;
} {
let hash = 0;
for (let i = 0; i < name.length; i++) {
hash = name.charCodeAt(i) + ((hash << 5) - hash);
}
const h = Math.abs(hash) % 360;
return {
backgroundColor: `hsl(${h} 55% 70%)`,
color: `hsl(${h} 55% 20%)`,
};
}

function OrgIcon({
org,
size = "sm",
}: {
org: { name: string; logo?: string | null };
size?: "xs" | "sm";
}) {
const sizeClass = size === "xs" ? "size-5" : "size-6";
const textClass = size === "xs" ? "text-[9px]" : "text-xs";

return (
<div
className={cn(
sizeClass,
"shrink-0 rounded-md flex items-center justify-center border border-border/50 overflow-hidden",
)}
style={org.logo ? undefined : getOrgColorStyle(org.name)}
>
{org.logo ? (
<img src={org.logo} alt="" className="size-full object-cover" />
) : (
<span className={cn("font-semibold leading-none", textClass)}>
{org.name.slice(0, 2).toUpperCase()}
</span>
)}
</div>
);
}

interface MenuItem {
key: string;
label: string;
Expand Down Expand Up @@ -141,112 +97,6 @@ function MenuItemButton({
);
}

function OrganizationsPanel({
orgParam,
onSelectOrg,
onCreateOrg,
}: {
orgParam?: string;
onSelectOrg: (slug: string) => void;
onCreateOrg: () => void;
}) {
// Fetched here, not in the parent: this panel only mounts inside the open
// popover/drawer, so the (potentially large) organization.list call is
// deferred until the switcher is actually opened — it no longer fires on
// every page load.
const { data: organizations } = useActiveOrganizations();
const sortedOrgs = [...(organizations ?? [])].sort((a, b) => {
if (a.slug === orgParam) return -1;
if (b.slug === orgParam) return 1;
return a.name.localeCompare(b.name);
});

const [searchOpen, setSearchOpen] = useState(false);
const [query, setQuery] = useState("");

const q = query.toLowerCase();
const filtered = q
? sortedOrgs.filter(
(o) =>
o.name.toLowerCase().includes(q) || o.slug.toLowerCase().includes(q),
)
: sortedOrgs;

const iconBtnClass =
"flex items-center justify-center size-7 rounded-md text-muted-foreground hover:bg-accent/50 hover:text-foreground transition-colors";

function toggleSearch() {
if (searchOpen) setQuery("");
setSearchOpen((prev) => !prev);
}

return (
<>
<div className="flex items-center justify-between px-4 py-3">
{searchOpen ? (
<input
autoFocus
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === "Escape" && toggleSearch()}
placeholder="Search organizations..."
className="flex-1 min-w-0 bg-transparent text-sm outline-none placeholder:text-muted-foreground/60"
/>
) : (
<span className="text-sm font-medium text-muted-foreground/60">
Your Organizations
</span>
)}
<div className="flex items-center gap-1 shrink-0">
<button type="button" onClick={toggleSearch} className={iconBtnClass}>
{searchOpen ? <XClose size={16} /> : <SearchMd size={16} />}
</button>
<button type="button" onClick={onCreateOrg} className={iconBtnClass}>
<Plus size={16} />
</button>
</div>
</div>
<div className="flex-1 min-h-0 overflow-y-auto p-1.5 flex flex-col gap-1">
{filtered.length === 0 && (
<p className="px-3 py-4 text-sm text-muted-foreground/60 text-center">
{query
? `No organizations match "${query}"`
: "No organizations available"}
</p>
)}
{filtered.map((org) => (
<button
key={org.id}
type="button"
onClick={() => onSelectOrg(org.slug)}
className={cn(
"flex items-center gap-3 px-3 py-2.5 rounded-md text-sm text-left w-full transition-colors",
org.slug === orgParam
? "bg-accent text-accent-foreground"
: "text-foreground hover:bg-accent/50",
)}
>
<OrgIcon org={org} size="sm" />
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{org.name}</p>
<p className="text-xs text-muted-foreground truncate">
{org.slug}
</p>
</div>
{org.slug === orgParam && (
<Check
size={14}
className="ml-auto text-muted-foreground shrink-0"
/>
)}
</button>
))}
</div>
</>
);
}

/** Shared content rendered inside popover (desktop) or drawer (mobile) */
function AccountPopoverContent({
user,
Expand Down
7 changes: 7 additions & 0 deletions apps/mesh/src/web/components/chat/chat-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,13 @@ interface TaskProviderInternals {
// ============================================================================

const ChatStreamCtx = createContext<ChatStreamContextValue | null>(null);
/**
* Exposed so Demo Mode (`web/demo/`) can supply a scripted `ChatStreamContextValue`
* directly — feeding the real chat renderers a recorded stream without the live
* `ActiveTaskProvider` (MCP transport, SSE, etc.). Not for app use; prefer the
* hooks. See `web/demo/demo-chat-stream.tsx`.
*/
export { ChatStreamCtx as DemoChatStreamContext };
const ChatTaskCtx = createContext<ChatTaskContextValue | null>(null);
const ChatPrefsCtx = createContext<ChatPrefsContextValue | null>(null);

Expand Down
6 changes: 6 additions & 0 deletions apps/mesh/src/web/components/chat/message/assistant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import type { ToolUIPart } from "ai";
import { type ReactNode, Suspense, useEffect, useState } from "react";
import { ToolCallShell } from "./parts/tool-call-part/common.tsx";
import { getPartRenderer as getExtraPartRenderer } from "./parts/extra-part-renderers.ts";
import type { ChatMessage } from "../types.ts";
import { MessageStatsBar } from "../usage-stats.tsx";
import { MessageTextPart } from "./parts/text-part.tsx";
Expand Down Expand Up @@ -517,6 +518,11 @@ function MessagePart({
const getSubtaskMeta = (toolCallId: string) =>
dataParts.toolSubtaskMetadata.get(toolCallId);

// Custom part renderers (e.g. Demo Mode) take precedence over the built-in
// switch. Empty registry in normal use — see extra-part-renderers.ts.
const extraRenderer = getExtraPartRenderer(part.type);
if (extraRenderer) return <>{extraRenderer(part as { type: string })}</>;

switch (part.type) {
case "dynamic-tool":
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Tiny extension point for rendering custom message parts inline in the chat.
*
* The core renderer (`message/assistant.tsx`) consults this registry before its
* built-in switch, so non-core surfaces (e.g. Demo Mode) can render their own
* part types in the real chat flow WITHOUT the core importing them. The registry
* is empty in normal use; a consumer registers a renderer at module load.
*/
import type { ReactNode } from "react";

type PartRenderer = (part: { type: string; output?: unknown }) => ReactNode;

const registry = new Map<string, PartRenderer>();

export function registerPartRenderer(type: string, render: PartRenderer): void {
registry.set(type, render);
}

export function getPartRenderer(type: string): PartRenderer | undefined {
return registry.get(type);
}
Loading
Loading