feat(22-00): DB migration, shared types, react-virtual, agent-role-colors, CSS animation
- Add updatedAt column to chat_messages schema (nullable) - Create migration 0048_add_chat_messages_updated_at.sql - Add updatedAt: string | null to ChatMessage shared type - Install @tanstack/react-virtual in ui workspace - Create agent-role-colors.ts with 11 distinct themed roles (THEME-03) - Add agent-role-colors.test.ts (4 tests all passing) - Add cursor-blink CSS animation with prefers-reduced-motion guard
This commit is contained in:
parent
25c87513f8
commit
6567ea700a
8 changed files with 141 additions and 1 deletions
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "chat_messages" ADD COLUMN "updated_at" timestamp with time zone DEFAULT now();
|
||||
|
|
@ -11,6 +11,7 @@ export const chatMessages = pgTable(
|
|||
content: text("content").notNull(),
|
||||
agentId: uuid("agent_id"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
conversationCreatedIdx: index("chat_messages_conversation_created_idx").on(table.conversationId, table.createdAt),
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export interface ChatMessage {
|
|||
content: string;
|
||||
agentId: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string | null;
|
||||
}
|
||||
|
||||
export interface ChatConversationListResponse {
|
||||
|
|
|
|||
75
pnpm-lock.yaml
generated
75
pnpm-lock.yaml
generated
|
|
@ -645,6 +645,9 @@ importers:
|
|||
'@tanstack/react-query':
|
||||
specifier: ^5.90.21
|
||||
version: 5.90.21(react@19.2.4)
|
||||
'@tanstack/react-virtual':
|
||||
specifier: ^3.13.23
|
||||
version: 3.13.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
|
|
@ -684,6 +687,9 @@ importers:
|
|||
react-router-dom:
|
||||
specifier: ^7.1.5
|
||||
version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
rehype-highlight:
|
||||
specifier: ^7.0.2
|
||||
version: 7.0.2
|
||||
remark-gfm:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
|
|
@ -3339,6 +3345,15 @@ packages:
|
|||
peerDependencies:
|
||||
react: ^18 || ^19
|
||||
|
||||
'@tanstack/react-virtual@3.13.23':
|
||||
resolution: {integrity: sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@tanstack/virtual-core@3.13.23':
|
||||
resolution: {integrity: sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
|
|
@ -4570,9 +4585,15 @@ packages:
|
|||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hast-util-is-element@3.0.0:
|
||||
resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
|
||||
|
||||
hast-util-to-jsx-runtime@2.3.6:
|
||||
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
|
||||
|
||||
hast-util-to-text@4.0.2:
|
||||
resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==}
|
||||
|
||||
hast-util-whitespace@3.0.0:
|
||||
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
||||
|
||||
|
|
@ -4583,6 +4604,10 @@ packages:
|
|||
resolution: {integrity: sha512-9D4SrmMXm4AhOZ08lnlGCZBzwfRnGjzZjkvPlkRfoPTODM2YeIAaEk+zO0vInh8DI4Qr3ySN8hLFxV1eKRYFaA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
highlight.js@11.11.1:
|
||||
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
html-encoding-sniffer@6.0.0:
|
||||
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||
|
|
@ -4768,6 +4793,7 @@ packages:
|
|||
|
||||
libsql@0.5.29:
|
||||
resolution: {integrity: sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg==}
|
||||
cpu: [x64, arm64, wasm32, arm]
|
||||
os: [darwin, linux, win32]
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
|
|
@ -4853,6 +4879,9 @@ packages:
|
|||
loupe@3.2.1:
|
||||
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
|
||||
|
||||
lowlight@3.3.0:
|
||||
resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==}
|
||||
|
||||
lru-cache@11.2.7:
|
||||
resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
|
@ -5484,6 +5513,9 @@ packages:
|
|||
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
||||
engines: {node: '>= 12.13.0'}
|
||||
|
||||
rehype-highlight@7.0.2:
|
||||
resolution: {integrity: sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==}
|
||||
|
||||
remark-gfm@4.0.1:
|
||||
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
|
||||
|
||||
|
|
@ -5821,6 +5853,9 @@ packages:
|
|||
unified@11.0.5:
|
||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||
|
||||
unist-util-find-after@5.0.0:
|
||||
resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
|
||||
|
||||
unist-util-is@6.0.1:
|
||||
resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
|
||||
|
||||
|
|
@ -9264,6 +9299,14 @@ snapshots:
|
|||
'@tanstack/query-core': 5.90.20
|
||||
react: 19.2.4
|
||||
|
||||
'@tanstack/react-virtual@3.13.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@tanstack/virtual-core': 3.13.23
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@tanstack/virtual-core@3.13.23': {}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.0
|
||||
|
|
@ -10553,6 +10596,10 @@ snapshots:
|
|||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hast-util-is-element@3.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
hast-util-to-jsx-runtime@2.3.6:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
|
|
@ -10573,6 +10620,13 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
hast-util-to-text@4.0.2:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/unist': 3.0.3
|
||||
hast-util-is-element: 3.0.0
|
||||
unist-util-find-after: 5.0.0
|
||||
|
||||
hast-util-whitespace@3.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
|
|
@ -10584,6 +10638,8 @@ snapshots:
|
|||
'@paperclipai/adapter-utils': 2026.325.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
highlight.js@11.11.1: {}
|
||||
|
||||
html-encoding-sniffer@6.0.0(@noble/hashes@2.0.1):
|
||||
dependencies:
|
||||
'@exodus/bytes': 1.15.0(@noble/hashes@2.0.1)
|
||||
|
|
@ -10826,6 +10882,12 @@ snapshots:
|
|||
|
||||
loupe@3.2.1: {}
|
||||
|
||||
lowlight@3.3.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
devlop: 1.1.0
|
||||
highlight.js: 11.11.1
|
||||
|
||||
lru-cache@11.2.7: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
|
|
@ -11816,6 +11878,14 @@ snapshots:
|
|||
|
||||
real-require@0.2.0: {}
|
||||
|
||||
rehype-highlight@7.0.2:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
hast-util-to-text: 4.0.2
|
||||
lowlight: 3.3.0
|
||||
unist-util-visit: 5.1.0
|
||||
vfile: 6.0.3
|
||||
|
||||
remark-gfm@4.0.1:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
|
|
@ -12228,6 +12298,11 @@ snapshots:
|
|||
trough: 2.2.0
|
||||
vfile: 6.0.3
|
||||
|
||||
unist-util-find-after@5.0.0:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
unist-util-is: 6.0.1
|
||||
|
||||
unist-util-is@6.0.1:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
|
|
|||
|
|
@ -41,14 +41,15 @@
|
|||
"@paperclipai/adapter-utils": "workspace:*",
|
||||
"@paperclipai/branding": "workspace:*",
|
||||
"@paperclipai/shared": "workspace:*",
|
||||
"hermes-paperclip-adapter": "^0.2.0",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"@tanstack/react-virtual": "^3.13.23",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"diff": "^8.0.4",
|
||||
"hermes-paperclip-adapter": "^0.2.0",
|
||||
"lexical": "0.35.0",
|
||||
"lucide-react": "^0.574.0",
|
||||
"mermaid": "^11.12.0",
|
||||
|
|
|
|||
|
|
@ -871,3 +871,19 @@ a.paperclip-project-mention-chip {
|
|||
[class*="_toolbarNodeKindSelectContainer_"] {
|
||||
z-index: 81 !important;
|
||||
}
|
||||
|
||||
@keyframes cursor-blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
.animate-cursor-blink {
|
||||
animation: cursor-blink 800ms step-start infinite;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.animate-cursor-blink {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
ui/src/lib/agent-role-colors.test.ts
Normal file
28
ui/src/lib/agent-role-colors.test.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { AGENT_ROLES } from "@paperclipai/shared";
|
||||
import { agentRoleColors, agentRoleColorDefault } from "./agent-role-colors";
|
||||
|
||||
describe("agentRoleColors", () => {
|
||||
it("has an entry for every AGENT_ROLES value", () => {
|
||||
for (const role of AGENT_ROLES) {
|
||||
expect(agentRoleColors[role]).toBeDefined();
|
||||
expect(agentRoleColors[role]).toContain("text-");
|
||||
}
|
||||
});
|
||||
|
||||
it("each entry has both light and dark variant", () => {
|
||||
for (const role of AGENT_ROLES) {
|
||||
expect(agentRoleColors[role]).toContain("dark:");
|
||||
}
|
||||
});
|
||||
|
||||
it("exports a default fallback color", () => {
|
||||
expect(agentRoleColorDefault).toBe("text-muted-foreground");
|
||||
});
|
||||
|
||||
it("all 11 roles have distinct color classes", () => {
|
||||
const colors = Object.values(agentRoleColors);
|
||||
const unique = new Set(colors);
|
||||
expect(unique.size).toBe(colors.length);
|
||||
});
|
||||
});
|
||||
17
ui/src/lib/agent-role-colors.ts
Normal file
17
ui/src/lib/agent-role-colors.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import type { AgentRole } from "@paperclipai/shared";
|
||||
|
||||
export const agentRoleColors: Record<AgentRole, string> = {
|
||||
pm: "text-blue-600 dark:text-blue-400",
|
||||
engineer: "text-violet-600 dark:text-violet-400",
|
||||
ceo: "text-amber-600 dark:text-amber-400",
|
||||
general: "text-slate-600 dark:text-slate-400",
|
||||
designer: "text-pink-600 dark:text-pink-400",
|
||||
qa: "text-orange-600 dark:text-orange-400",
|
||||
researcher: "text-teal-600 dark:text-teal-400",
|
||||
devops: "text-emerald-600 dark:text-emerald-400",
|
||||
cto: "text-indigo-600 dark:text-indigo-400",
|
||||
cmo: "text-rose-600 dark:text-rose-400",
|
||||
cfo: "text-cyan-600 dark:text-cyan-400",
|
||||
};
|
||||
|
||||
export const agentRoleColorDefault = "text-muted-foreground";
|
||||
Loading…
Add table
Reference in a new issue