Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
0213e14
feat(appkit): reference agent-app, dev-playground chat UI, docs, and …
MarioCadenas Apr 21, 2026
0adf359
fix(appkit): align chat clients + template with renamed 'agents' plugin
MarioCadenas Apr 22, 2026
3ab0ce6
docs(agents): folder layout on disk, migrate samples, sync API refs
MarioCadenas Apr 23, 2026
88c051f
docs(appkit): regenerate typedoc API reference for folder-agents loader
MarioCadenas Apr 23, 2026
f1097f9
feat(dev-playground): port Smart Dashboard as /smart-dashboard route;…
MarioCadenas Apr 24, 2026
12b49b6
feat(dev-playground): stage 2-4 of smart-dashboard demo
MarioCadenas Apr 24, 2026
e4f40c2
feat(appkit): sub-agent approval gate + save view to volume + saved v…
MarioCadenas May 4, 2026
f682e1d
fix(playground): treat missing saved-views dir as empty list, not 500
MarioCadenas Apr 24, 2026
b85688d
fix(appkit): forward all sub-agent events except metadata
MarioCadenas May 4, 2026
8924883
fix(playground): use html2canvas-pro to support oklch() colors
MarioCadenas Apr 24, 2026
eeb5060
fix(playground): unwrap DownloadResponse when serving saved-view PNGs
MarioCadenas Apr 24, 2026
6f21dbc
fix(playground): apply saved view directly from metadata on thumbnail…
MarioCadenas Apr 24, 2026
b3422df
docs(appkit): regenerate typedoc for tool annotations
MarioCadenas Apr 24, 2026
bb3796b
feat(playground): revamp smart dashboard with denser charts and actio…
MarioCadenas Apr 24, 2026
acade71
feat(playground): hamburger nav with shared catalog and redesigned home
MarioCadenas Apr 24, 2026
9daf107
feat(playground): tiered approval card — writes vs updates vs destruc…
MarioCadenas Apr 24, 2026
42d658e
fix(playground): pin agent-feed card tints to sRGB hex
MarioCadenas Apr 24, 2026
ce326d3
fix(playground): gate Tailwind dark: variant on the theme class
MarioCadenas Apr 24, 2026
a934113
fix(playground): stop streaming chat bubbles from pulsing
MarioCadenas Apr 27, 2026
c819b40
chore(playground): migrate dev-playground server to onPluginsReady
MarioCadenas Apr 27, 2026
929b0e6
feat(template): scaffold a working starter agent
MarioCadenas Apr 29, 2026
1c86186
fix(playground, template): import agents from @databricks/appkit/beta
MarioCadenas May 4, 2026
6800f3a
docs: beta agents banner, template stability, and unified typedoc entry
MarioCadenas May 4, 2026
62a687f
chore: remove plans scratch docs from agents stack branch
MarioCadenas May 4, 2026
7b3d48b
chore(appkit): regenerate typedoc and sync lockfile after rebase
MarioCadenas May 7, 2026
60ba6f1
chore(appkit): regenerate typedoc after rebase onto v5
MarioCadenas May 7, 2026
19e572f
chore(playground): migrate dev-playground to tools(plugins) function …
MarioCadenas May 8, 2026
62599fc
chore(appkit): regenerate typedoc after tools(plugins) migration
MarioCadenas May 8, 2026
caf04c8
chore(appkit): regenerate typedoc after dropping RegisteredPlugins
MarioCadenas May 8, 2026
cbd35ab
fix(agents): unhide manifest so plugin exports from @databricks/appki…
MarioCadenas May 8, 2026
00e5702
chore(appkit): regenerate typedoc after runAgent docstring update
MarioCadenas May 8, 2026
fed3cc8
feat(appkit): unified tools list in markdown agent frontmatter
MarioCadenas May 8, 2026
b933498
chore: package-lock
MarioCadenas May 11, 2026
63f8cce
fix(appkit): tool() name optional, execute returns unknown, docs accu…
MarioCadenas May 11, 2026
d82c518
fix(appkit): mcp client hardening + reload() race
MarioCadenas May 11, 2026
c557334
feat(appkit-ui): useAgentChat React hook wrapping connectSSE
MarioCadenas May 11, 2026
17117dc
fix(template): read agents list from clientConfig, drop /api/agents/i…
MarioCadenas May 11, 2026
b67371a
refactor(playground): migrate useAgentStream to wrap useAgentChat
MarioCadenas May 11, 2026
7bb3d1d
revert(changelog): drop manual edits, release-it owns CHANGELOG.md
MarioCadenas May 11, 2026
c6c670e
refactor(appkit): unify tool callbacks, migrate to effect enum, valid…
MarioCadenas May 11, 2026
0f489ba
refactor(appkit): mcp connectAll partial failures + tool description …
MarioCadenas May 11, 2026
64ffad3
chore: fix CI integration env + reviewer template/docs polish
MarioCadenas May 11, 2026
82b895f
feat(template): bring back the markdown-agent demo as `planner`
MarioCadenas May 11, 2026
7537a94
feat(template): collapse template chat to one agent backed by a sub-a…
MarioCadenas May 11, 2026
18043af
refactor(playground): drop featured demo card from landing page
MarioCadenas May 11, 2026
514b940
fix(playground): split SavedViewsPanel header to avoid nested button
MarioCadenas May 11, 2026
fd4dd96
fix(appkit): preserve Vertex thought_signature on tool_call round-trips
MarioCadenas May 11, 2026
c4fc7ed
fix(template): bump zod to match AppKit, guard future drift
MarioCadenas May 11, 2026
aa58ff4
chore: package-lock
MarioCadenas May 11, 2026
90751bb
refactor(appkit): drop unused snake_case thought_signature handling
MarioCadenas May 12, 2026
91c9166
docs(appkit): tighten agents plugin resource description for CLI
MarioCadenas May 12, 2026
2448455
chore(appkit): address PR #306 agentic review findings
MarioCadenas May 12, 2026
d97fbf2
fix(appkit): raise default express.json body limit to 1mb
MarioCadenas May 12, 2026
526ad84
chore(template): sync agents plugin description from manifest
MarioCadenas May 12, 2026
e1481c3
chore: fixup
MarioCadenas May 12, 2026
abe81d4
chore: fixup
MarioCadenas May 12, 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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Comment thread
MarioCadenas marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ jobs:
APPKIT_E2E_TEST: 'true'
DATABRICKS_WAREHOUSE_ID: e2e-mock
DATABRICKS_WORKSPACE_ID: e2e-mock
DATABRICKS_SERVING_ENDPOINT_NAME: e2e-mock

pr-template-artifact:
name: PR Template Artifact
Expand Down
64 changes: 64 additions & 0 deletions apps/dev-playground/client/package-lock.json

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

4 changes: 3 additions & 1 deletion apps/dev-playground/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"@tanstack/router-plugin": "1.133.22",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"html2canvas": "1.4.1",
"html2canvas-pro": "2.0.2",
"lucide-react": "0.546.0",
"react": "19.2.0",
"react-dom": "19.2.0",
Expand All @@ -30,6 +32,7 @@
},
"devDependencies": {
"@eslint/js": "9.36.0",
"@tailwindcss/postcss": "4.1.17",
"@tanstack/router-cli": "1.133.20",
"@types/node": "24.6.0",
"@types/react": "19.2.2",
Expand All @@ -43,7 +46,6 @@
"postcss": "8.5.6",
"shiki": "3.15.0",
"tailwindcss": "4.1.17",
"@tailwindcss/postcss": "4.1.17",
"typescript": "5.9.3",
"typescript-eslint": "8.45.0",
"vite": "npm:rolldown-vite@7.1.14"
Expand Down
Comment thread
pkosiec marked this conversation as resolved.
Comment thread
pkosiec marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { CheckCircle2Icon } from "lucide-react";
import { useEffect, useState } from "react";

interface ActionToastProps {
/**
* Latest dispatcher-surfaced action summary. Each new value bumps a
* render key so the toast re-animates even if the same message arrives
* twice (e.g. two identical filter calls in a row).
*/
message: string | null;
durationMs?: number;
}

/**
* Non-intrusive bottom-left toast that confirms every agent-driven UI
* action. Silent success was the worst failure mode before: an action
* silently not-applied looked identical to one that worked but didn't
* show its effect.
*/
export function ActionToast({ message, durationMs = 2800 }: ActionToastProps) {
const [visible, setVisible] = useState<{ key: number; text: string } | null>(
null,
);

useEffect(() => {
if (!message) return;
const key = Date.now();
setVisible({ key, text: message });
const t = setTimeout(() => {
setVisible((v) => (v?.key === key ? null : v));
}, durationMs);
return () => {
clearTimeout(t);
};
}, [message, durationMs]);

if (!visible) return null;

return (
<div
key={visible.key}
className="fixed bottom-20 left-4 z-30 rounded-full bg-card border border-border shadow-lg px-3 py-1.5 flex items-center gap-2 animate-in fade-in slide-in-from-bottom-2 duration-200"
>
<CheckCircle2Icon className="h-3.5 w-3.5 text-green-500 shrink-0" />
<span className="text-xs text-foreground">{visible.text}</span>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import {
AlertTriangleIcon,
ArrowRightIcon,
CalendarIcon,
CrosshairIcon,
DollarSignIcon,
HighlighterIcon,
LightbulbIcon,
MapPinIcon,
MessageSquareIcon,
} from "lucide-react";
import type { FeedAction } from "../lib/feed-actions";

type Variant = "insight" | "anomaly";
type Severity = "low" | "medium" | "high";

interface ActionableCardProps {
variant: Variant;
severity?: Severity;
title: string;
description: string;
actions: FeedAction[];
/** Fired for non-ask actions. Route applies them to dashboard state. */
onAction: (action: FeedAction) => void;
/** Fired for `ask` actions. Route forwards the prompt to the chat drawer. */
onAsk: (prompt: string) => void;
}

// Backgrounds are written as arbitrary 8-digit hex (e.g. `bg-[#eff6ff80]`)
// instead of Tailwind's `/N` alpha shorthand. Rationale: `bg-blue-50/50`
// compiles in Tailwind v4 to a pair — an sRGB hex fallback and a
// `@supports (color-mix)` override that re-mixes in oklab over the oklch
// palette token. Browsers that support `color-mix` (recent Chrome/Arc) take
// the oklab path; older embedded Chromiums (e.g. Cursor's built-in browser
// at the time of writing) fall through to the sRGB hex. Because oklab and
// sRGB interpolation produce visibly different tints — especially against
// the dark `--card` token — the same card ends up looking different in each
// browser. Pinning the colour to a literal hex (no `/N`, no @supports
// override) keeps all browsers on the same sRGB path and therefore the same
// visual result.
const INSIGHT_STYLES = {
border: "border-blue-200 dark:border-blue-900",
bg: "bg-[#eff6ff80] dark:bg-[#1624564d]",
icon: "text-blue-500",
};

const ANOMALY_STYLES: Record<
Severity,
{ border: string; bg: string; icon: string; badge: string }
> = {
low: {
border: "border-yellow-200 dark:border-yellow-900",
bg: "bg-[#fefce880] dark:bg-[#4320044d]",
icon: "text-yellow-500",
badge:
"bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-400",
},
medium: {
border: "border-orange-200 dark:border-orange-900",
bg: "bg-[#fff7ed80] dark:bg-[#4413064d]",
icon: "text-orange-500",
badge:
"bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-400",
},
high: {
border: "border-red-200 dark:border-red-900",
bg: "bg-[#fef2f280] dark:bg-[#4608094d]",
icon: "text-red-500",
badge: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-400",
},
};

function iconForAction(kind: FeedAction["kind"]): React.ReactNode {
const cls = "h-3 w-3";
switch (kind) {
case "filter_date":
return <CalendarIcon className={cls} />;
case "filter_zip":
return <MapPinIcon className={cls} />;
case "filter_fare":
return <DollarSignIcon className={cls} />;
case "highlight_period":
return <HighlighterIcon className={cls} />;
case "highlight_zone":
return <MapPinIcon className={cls} />;
case "focus_chart":
return <CrosshairIcon className={cls} />;
case "ask":
return <MessageSquareIcon className={cls} />;
}
}

/**
* Action chip for a single feed suggestion. The chip's visual weight depends
* on its kind: structural mutations (filter/highlight/focus) use the primary
* tint, `ask` uses a neutral outline so the user can tell "this opens the
* chat" from "this changes the dashboard" without reading the label.
*/
function ActionChip({
action,
onAction,
onAsk,
}: {
action: FeedAction;
onAction: (a: FeedAction) => void;
onAsk: (prompt: string) => void;
}) {
const isAsk = action.kind === "ask";
const isHighlight =
action.kind === "highlight_period" || action.kind === "highlight_zone";

return (
<button
type="button"
onClick={() => {
if (isAsk) onAsk(action.prompt);
else onAction(action);
}}
className={`inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded-md transition-colors ${
isAsk
? "border border-border bg-background text-foreground/80 hover:bg-muted hover:text-foreground"
: isHighlight
? "bg-amber-100 text-amber-800 hover:bg-amber-200 dark:bg-amber-900/40 dark:text-amber-200 dark:hover:bg-amber-900/60"
: "bg-primary/10 text-primary hover:bg-primary/20"
}`}
>
{iconForAction(action.kind)}
<span>{action.label}</span>
{isAsk && <ArrowRightIcon className="h-3 w-3 opacity-70" />}
</button>
);
}

export function ActionableCard({
variant,
severity,
title,
description,
actions,
onAction,
onAsk,
}: ActionableCardProps) {
const isAnomaly = variant === "anomaly";
const styles = isAnomaly
? ANOMALY_STYLES[severity ?? "low"]
: { ...INSIGHT_STYLES, badge: "" };

return (
<div className={`rounded-lg border ${styles.border} ${styles.bg} p-3`}>
<div className="flex items-start gap-2 mb-2">
{isAnomaly ? (
<AlertTriangleIcon
className={`h-4 w-4 ${styles.icon} mt-0.5 shrink-0`}
/>
) : (
<LightbulbIcon className={`h-4 w-4 ${styles.icon} mt-0.5 shrink-0`} />
)}
<div className="min-w-0 flex-1">
<div className="flex items-start gap-2">
<p className="text-sm font-medium text-foreground leading-tight flex-1">
{title}
</p>
{isAnomaly && severity && (
<span
className={`text-[10px] font-medium px-1.5 py-0.5 rounded shrink-0 ${styles.badge}`}
>
{severity}
</span>
)}
</div>
<p className="text-xs text-muted-foreground mt-1 leading-relaxed">
{description}
</p>
</div>
</div>

{actions.length > 0 && (
<div className="flex flex-wrap gap-1.5 pl-6">
{actions.map((action, i) => (
<ActionChip
key={`${action.kind}-${i}-${action.label}`}
action={action}
onAction={onAction}
onAsk={onAsk}
/>
))}
</div>
)}
</div>
);
}
Loading
Loading