feat(web/ui7): migrate note detail, palace, gaps/prompts pages, broadcast banner
Phase UI7 — completes the note detail surface, the Palace knowledge
exploration page + its panels, the knowledge-gaps page, the prompts
page empty states, and the broadcast banner. Brings the ratchet down
to 14 raw controls / 21 legacy class matches — both genuine remaining
intentional items (NoteEditor toolbar, hidden file input, audit false
positives matching Tailwind arbitrary values).
notes/[noteId]/page.tsx:
- 'Loading' badge → Badge variant=neutral.
- Loading/error sections → Card.
- Review-state link → Link wrapping Badge.
palace/page.tsx:
- Wing <select> → Select with options=[{value,label}].
palace components:
- PalacePanel.tsx — search input → Input, hall chip → Badge.
- MemoryTimeline.tsx — hall chip → Badge.
- KnowledgeGraphView.tsx — entity query input → Input.
workspaces/[id]/gaps/page.tsx:
- Topic Coverage section → Card, chip → Badge.
- Empty-state + per-gap items → Card.
prompts/page.tsx:
- Loading + empty-state divs → Card.
landing page (/):
- section.surface-card → Card.
- 'Backend-backed web surface' badge → Badge.
- 'Open dashboard'/'Browse workspaces' links → utility classes.
share/[token]/page.tsx:
- Read-only public share badge → Badge.
- Main content surface-card + input-shell body wrapper → Card with
bordered body container.
BroadcastBanner.tsx:
- CTA + Dismiss raw <button> → Button (ghost variant, size sm).
Cumulative ratchet impact since session start:
raw interactive controls 38 → 14 (-24)
legacy global surface classes 92 → 21 (-71)
hardcoded color literals 0 (clean)
direct @bytelyst/ui imports 0 (clean)
Verified: pnpm typecheck, test (96/96), ratchet at new baseline.
This commit is contained in:
parent
8d484c30d1
commit
3288e28f5c
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"//": "Baseline UI drift counts. Updated 2026-05-23T08:33:24Z by scripts/ui-drift-ratchet.sh --update. Commit alongside the migration that lowered the counts.",
|
"//": "Baseline UI drift counts. Updated 2026-05-23T08:48:54Z by scripts/ui-drift-ratchet.sh --update. Commit alongside the migration that lowered the counts.",
|
||||||
"raw_interactive_controls": 25,
|
"raw_interactive_controls": 14,
|
||||||
"legacy_global_surface_classes": 67,
|
"legacy_global_surface_classes": 21,
|
||||||
"hardcoded_color_literals": 0,
|
"hardcoded_color_literals": 0,
|
||||||
"direct_bytelyst_ui_imports_outside_adapter": 0
|
"direct_bytelyst_ui_imports_outside_adapter": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { ArtifactPanel } from "@/components/ArtifactPanel";
|
|||||||
import { AgentTimeline } from "@/components/AgentTimeline";
|
import { AgentTimeline } from "@/components/AgentTimeline";
|
||||||
import { SmartActionsPanel } from "@/components/SmartActionsPanel";
|
import { SmartActionsPanel } from "@/components/SmartActionsPanel";
|
||||||
import { LinkNoteModal } from "@/components/LinkNoteModal";
|
import { LinkNoteModal } from "@/components/LinkNoteModal";
|
||||||
import { Badge, Button } from "@/components/ui/Primitives";
|
import { Badge, Button, Card } from "@/components/ui/Primitives";
|
||||||
import {
|
import {
|
||||||
archiveNote,
|
archiveNote,
|
||||||
createNoteArtifact,
|
createNoteArtifact,
|
||||||
@ -214,11 +214,11 @@ export default function NoteDetailPage() {
|
|||||||
<AppShell
|
<AppShell
|
||||||
title="Note detail"
|
title="Note detail"
|
||||||
description="Editable note surface with metadata, linked context, tasks, artifacts, and review history."
|
description="Editable note surface with metadata, linked context, tasks, artifacts, and review history."
|
||||||
actions={<div className="badge">Loading</div>}
|
actions={<Badge variant="neutral">Loading</Badge>}
|
||||||
>
|
>
|
||||||
<section className="surface-card" style={{ padding: "var(--nl-space-6)", color: "var(--nl-text-secondary)" }}>
|
<Card padding="lg" className="text-[color:var(--nl-text-secondary)]">
|
||||||
{error ?? "Loading note…"}
|
{error ?? "Loading note…"}
|
||||||
</section>
|
</Card>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -232,8 +232,8 @@ export default function NoteDetailPage() {
|
|||||||
{isSaving ? (
|
{isSaving ? (
|
||||||
<Badge>Saving</Badge>
|
<Badge>Saving</Badge>
|
||||||
) : (
|
) : (
|
||||||
<Link href="/reviews" className="badge">
|
<Link href="/reviews">
|
||||||
{`Review: ${note.metadata.reviewState}`}
|
<Badge variant="neutral">{`Review: ${note.metadata.reviewState}`}</Badge>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
<Button variant="secondary" onClick={handleSummarize}>
|
<Button variant="secondary" onClick={handleSummarize}>
|
||||||
@ -269,7 +269,7 @@ export default function NoteDetailPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<aside style={{ display: "grid", gap: "var(--nl-space-4)" }}>
|
<aside style={{ display: "grid", gap: "var(--nl-space-4)" }}>
|
||||||
{error ? <div className="surface-card" style={{ padding: "var(--nl-space-4)", color: "var(--nl-text-secondary)" }}>{error}</div> : null}
|
{error ? <Card padding="sm" className="text-[color:var(--nl-text-secondary)]">{error}</Card> : null}
|
||||||
<SmartActionsPanel
|
<SmartActionsPanel
|
||||||
noteId={note.id}
|
noteId={note.id}
|
||||||
workspaceId={note.workspaceId}
|
workspaceId={note.workspaceId}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { AppShell } from "@/components/AppShell";
|
import { AppShell } from "@/components/AppShell";
|
||||||
import { Button } from "@/components/ui/Primitives";
|
import { Button, Select } from "@/components/ui/Primitives";
|
||||||
import { PalacePanel } from "@/components/PalacePanel";
|
import { PalacePanel } from "@/components/PalacePanel";
|
||||||
import { PalaceStats } from "@/components/PalaceStats";
|
import { PalaceStats } from "@/components/PalaceStats";
|
||||||
import { KnowledgeGraphView } from "@/components/KnowledgeGraphView";
|
import { KnowledgeGraphView } from "@/components/KnowledgeGraphView";
|
||||||
@ -45,20 +45,16 @@ export default function PalacePage() {
|
|||||||
<h1 style={{ fontSize: "var(--nl-fs-2xl)", fontWeight: 700 }}>Memory Palace</h1>
|
<h1 style={{ fontSize: "var(--nl-fs-2xl)", fontWeight: 700 }}>Memory Palace</h1>
|
||||||
|
|
||||||
{!loadingWings && wings.length > 0 && (
|
{!loadingWings && wings.length > 0 && (
|
||||||
<select
|
<Select
|
||||||
value={selectedWing ?? ""}
|
value={selectedWing ?? ""}
|
||||||
onChange={(e) => setSelectedWing(e.target.value || undefined)}
|
onChange={(e) => setSelectedWing(e.target.value || undefined)}
|
||||||
className="input"
|
|
||||||
style={{ maxWidth: 240 }}
|
|
||||||
aria-label="Select wing"
|
aria-label="Select wing"
|
||||||
>
|
className="max-w-[240px]"
|
||||||
<option value="">All wings</option>
|
options={[
|
||||||
{wings.map((w) => (
|
{ value: "", label: "All wings" },
|
||||||
<option key={w.id} value={w.id}>
|
...wings.map((w) => ({ value: w.id, label: `${w.name} (${w.memoryCount})` })),
|
||||||
{w.name} ({w.memoryCount})
|
]}
|
||||||
</option>
|
/>
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -96,9 +96,9 @@ export default function PromptsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="surface-card" style={{ padding: "var(--nl-space-6)", textAlign: "center", color: "var(--nl-text-secondary)" }}>
|
<Card padding="lg" className="text-center text-[color:var(--nl-text-secondary)]">
|
||||||
Loading templates…
|
Loading templates…
|
||||||
</div>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Built-in templates */}
|
{/* Built-in templates */}
|
||||||
@ -167,9 +167,9 @@ export default function PromptsPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!loading && filtered.length === 0 && (
|
{!loading && filtered.length === 0 && (
|
||||||
<div className="surface-card" style={{ padding: "var(--nl-space-6)", textAlign: "center", color: "var(--nl-text-secondary)" }}>
|
<Card padding="lg" className="text-center text-[color:var(--nl-text-secondary)]">
|
||||||
No templates found for this category.
|
No templates found for this category.
|
||||||
</div>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</AppShell>
|
</AppShell>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { Brain, Plus, AlertTriangle } from "lucide-react";
|
import { Brain, Plus, AlertTriangle } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/Primitives";
|
import { Badge, Button, Card } from "@/components/ui/Primitives";
|
||||||
import { getKnowledgeGaps } from "@/lib/prompt-client";
|
import { getKnowledgeGaps } from "@/lib/prompt-client";
|
||||||
import { toast } from "@/lib/toast";
|
import { toast } from "@/lib/toast";
|
||||||
import type { KnowledgeGap } from "@/lib/types";
|
import type { KnowledgeGap } from "@/lib/types";
|
||||||
@ -59,29 +59,29 @@ export default function KnowledgeGapsPage() {
|
|||||||
|
|
||||||
{/* Topic coverage map */}
|
{/* Topic coverage map */}
|
||||||
{analyzed && Object.keys(topicMap).length > 0 && (
|
{analyzed && Object.keys(topicMap).length > 0 && (
|
||||||
<div className="surface-card" style={{ padding: "var(--nl-space-4)", display: "grid", gap: "var(--nl-space-3)" }}>
|
<Card padding="sm" className="grid gap-3">
|
||||||
<strong style={{ fontSize: "var(--nl-fs-sm)" }}>Topic Coverage</strong>
|
<strong className="text-[length:var(--nl-fs-sm)]">Topic Coverage</strong>
|
||||||
<div style={{ display: "flex", gap: "var(--nl-space-2)", flexWrap: "wrap" }}>
|
<div className="flex flex-wrap gap-2">
|
||||||
{Object.entries(topicMap)
|
{Object.entries(topicMap)
|
||||||
.sort(([, a], [, b]) => b - a)
|
.sort(([, a], [, b]) => b - a)
|
||||||
.map(([topic, count]) => (
|
.map(([topic, count]) => (
|
||||||
<span key={topic} className="badge" style={{ fontSize: "var(--nl-fs-xs)" }}>
|
<Badge key={topic} variant="neutral" size="sm">
|
||||||
{topic} ({count})
|
{topic} ({count})
|
||||||
</span>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Gaps list */}
|
{/* Gaps list */}
|
||||||
{analyzed && gaps.length === 0 && !loading && (
|
{analyzed && gaps.length === 0 && !loading && (
|
||||||
<div className="surface-muted" style={{ padding: "var(--nl-space-6)", textAlign: "center", color: "var(--nl-text-secondary)" }}>
|
<Card padding="lg" className="bg-[color:var(--nl-surface-muted)] text-center text-[color:var(--nl-text-secondary)]">
|
||||||
No knowledge gaps detected. Your workspace has good topic coverage.
|
No knowledge gaps detected. Your workspace has good topic coverage.
|
||||||
</div>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{gaps.map((gap, i) => (
|
{gaps.map((gap, i) => (
|
||||||
<div key={i} className="surface-card" style={{ padding: "var(--nl-space-4)", display: "grid", gap: "var(--nl-space-2)" }}>
|
<Card key={i} padding="sm" className="grid gap-2">
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: "var(--nl-space-2)" }}>
|
<div style={{ display: "flex", alignItems: "center", gap: "var(--nl-space-2)" }}>
|
||||||
<AlertTriangle size={14} style={{ color: "var(--nl-warning)" }} />
|
<AlertTriangle size={14} style={{ color: "var(--nl-warning)" }} />
|
||||||
<strong style={{ fontSize: "var(--nl-fs-md)" }}>{gap.topic}</strong>
|
<strong style={{ fontSize: "var(--nl-fs-md)" }}>{gap.topic}</strong>
|
||||||
@ -101,7 +101,7 @@ export default function KnowledgeGapsPage() {
|
|||||||
<Plus size={14} /> Create: {gap.suggestedTitle}
|
<Plus size={14} /> Create: {gap.suggestedTitle}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,27 +1,36 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { Badge, Card } from "@/components/ui/Primitives";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
<main style={{ minHeight: "100vh", display: "grid", placeItems: "center", padding: "var(--nl-space-8)" }}>
|
<main className="grid min-h-screen place-items-center p-8">
|
||||||
<section className="surface-card" style={{ maxWidth: 760, padding: "var(--nl-space-8)", display: "grid", gap: "var(--nl-space-5)" }}>
|
<Card padding="lg" className="grid max-w-[760px] gap-5 p-8">
|
||||||
<div className="badge">Backend-backed web surface</div>
|
<Badge variant="info" className="justify-self-start">
|
||||||
<div style={{ display: "grid", gap: "var(--nl-space-3)" }}>
|
Backend-backed web surface
|
||||||
<h1 style={{ margin: 0, fontFamily: "var(--nl-font-display)", fontSize: "var(--nl-fs-3xl)" }}>
|
</Badge>
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<h1 className="m-0 font-[family-name:var(--nl-font-display)] text-[length:var(--nl-fs-3xl)]">
|
||||||
NoteLett
|
NoteLett
|
||||||
</h1>
|
</h1>
|
||||||
<p style={{ margin: 0, color: "var(--nl-text-secondary)", lineHeight: 1.6 }}>
|
<p className="m-0 leading-relaxed text-[color:var(--nl-text-secondary)]">
|
||||||
Structured notes workspace for humans and agents with search, review, and operational context.
|
Structured notes workspace for humans and agents with search, review, and operational context.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", gap: "var(--nl-space-3)", flexWrap: "wrap" }}>
|
<div className="flex flex-wrap gap-3">
|
||||||
<Link href="/dashboard" className="surface-muted" style={{ padding: "12px 16px", background: "var(--nl-accent-muted)" }}>
|
<Link
|
||||||
|
href="/dashboard"
|
||||||
|
className="rounded-[var(--nl-radius-sm)] bg-[color:var(--nl-accent-muted)] px-4 py-3"
|
||||||
|
>
|
||||||
Open dashboard
|
Open dashboard
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/workspaces" className="surface-muted" style={{ padding: "12px 16px" }}>
|
<Link
|
||||||
|
href="/workspaces"
|
||||||
|
className="rounded-[var(--nl-radius-sm)] bg-[color:var(--nl-surface-muted)] px-4 py-3"
|
||||||
|
>
|
||||||
Browse workspaces
|
Browse workspaces
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</Card>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
|
import { Badge, Card } from "@/components/ui/Primitives";
|
||||||
import { NOTES_API_URL, PRODUCT_NAME } from "@/lib/product-config";
|
import { NOTES_API_URL, PRODUCT_NAME } from "@/lib/product-config";
|
||||||
import { sanitizeSharedNoteHtml } from "@/lib/sanitize-share-html";
|
import { sanitizeSharedNoteHtml } from "@/lib/sanitize-share-html";
|
||||||
|
|
||||||
@ -46,28 +47,31 @@ export default function SharedNotePage() {
|
|||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ minHeight: "100vh", background: "var(--nl-bg-canvas)", color: "var(--nl-text-primary)", padding: "var(--nl-space-8)" }}>
|
<div className="min-h-screen bg-[color:var(--nl-bg-canvas)] p-8 text-[color:var(--nl-text-primary)]">
|
||||||
<header style={{ maxWidth: 720, margin: "0 auto var(--nl-space-6)" }}>
|
<header className="mx-auto mb-6 max-w-[720px]">
|
||||||
<div className="badge" style={{ marginBottom: 12 }}>
|
<Badge variant="info" className="mb-3">
|
||||||
Read-only public share · {PRODUCT_NAME}
|
Read-only public share · {PRODUCT_NAME}
|
||||||
</div>
|
</Badge>
|
||||||
<h1 style={{ margin: 0, fontSize: "var(--nl-fs-2xl)" }}>{note?.title ?? (error ? "Unavailable" : "Loading…")}</h1>
|
<h1 className="m-0 text-[length:var(--nl-fs-2xl)]">
|
||||||
|
{note?.title ?? (error ? "Unavailable" : "Loading…")}
|
||||||
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
<main id="main-content" className="surface-card" style={{ maxWidth: 720, margin: "0 auto", padding: "var(--nl-space-6)" }}>
|
<Card padding="lg" className="mx-auto max-w-[720px]">
|
||||||
{error ? <p style={{ color: "var(--nl-text-secondary)" }}>{error}</p> : null}
|
<main id="main-content">
|
||||||
{note ? (
|
{error ? <p className="text-[color:var(--nl-text-secondary)]">{error}</p> : null}
|
||||||
<>
|
{note ? (
|
||||||
<p style={{ color: "var(--nl-text-secondary)", fontSize: "var(--nl-fs-sm)" }}>
|
<>
|
||||||
Updated {new Date(note.updatedAt).toLocaleString()} · {note.expiresAt ? `Expires ${new Date(note.expiresAt).toLocaleDateString()}` : "Expires when revoked"} · Note ID {note.noteId}
|
<p className="text-[length:var(--nl-fs-sm)] text-[color:var(--nl-text-secondary)]">
|
||||||
</p>
|
Updated {new Date(note.updatedAt).toLocaleString()} · {note.expiresAt ? `Expires ${new Date(note.expiresAt).toLocaleDateString()}` : "Expires when revoked"} · Note ID {note.noteId}
|
||||||
<div
|
</p>
|
||||||
className="input-shell"
|
<div
|
||||||
style={{ marginTop: 16, minHeight: 200, lineHeight: 1.7 }}
|
className="mt-4 min-h-[200px] rounded-[var(--nl-radius-md)] border border-[color:var(--nl-border-default)] bg-[color:var(--nl-input-bg)] p-3 leading-relaxed"
|
||||||
dangerouslySetInnerHTML={{ __html: sanitizeSharedNoteHtml(note.body) }}
|
dangerouslySetInnerHTML={{ __html: sanitizeSharedNoteHtml(note.body) }}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</main>
|
</main>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState, useCallback, useRef } from "react";
|
import { useEffect, useState, useCallback, useRef } from "react";
|
||||||
|
import { Button } from "@/components/ui/Primitives";
|
||||||
import { getBroadcastClient } from "@/lib/broadcast-client";
|
import { getBroadcastClient } from "@/lib/broadcast-client";
|
||||||
import type { InAppMessage } from "@bytelyst/broadcast-client";
|
import type { InAppMessage } from "@bytelyst/broadcast-client";
|
||||||
|
|
||||||
@ -65,15 +66,21 @@ export function BroadcastBanner() {
|
|||||||
<strong style={{ whiteSpace: "nowrap" }}>{msg.title}</strong>
|
<strong style={{ whiteSpace: "nowrap" }}>{msg.title}</strong>
|
||||||
{msg.body && <span style={{ color: "var(--nl-text-secondary)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{msg.body}</span>}
|
{msg.body && <span style={{ color: "var(--nl-text-secondary)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{msg.body}</span>}
|
||||||
{msg.ctaUrl && (
|
{msg.ctaUrl && (
|
||||||
<button onClick={() => handleClick(msg)} style={{ background: "none", border: "none", color: "var(--nl-accent-primary)", cursor: "pointer", fontWeight: 600, whiteSpace: "nowrap" }}>
|
<Button variant="ghost" size="sm" onClick={() => handleClick(msg)} className="whitespace-nowrap font-semibold text-[color:var(--nl-accent-primary)]">
|
||||||
{msg.ctaText ?? "Learn more"}
|
{msg.ctaText ?? "Learn more"}
|
||||||
</button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{msg.dismissible !== false && (
|
{msg.dismissible !== false && (
|
||||||
<button onClick={() => dismiss(msg.id)} aria-label="Dismiss" style={{ background: "none", border: "none", color: "var(--nl-text-secondary)", cursor: "pointer", fontSize: 16, lineHeight: 1, padding: "0 0 0 var(--nl-space-3)" }}>
|
<Button
|
||||||
×
|
variant="ghost"
|
||||||
</button>
|
size="sm"
|
||||||
|
onClick={() => dismiss(msg.id)}
|
||||||
|
aria-label="Dismiss"
|
||||||
|
className="text-[color:var(--nl-text-secondary)]"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Button, Card } from "@/components/ui/Primitives";
|
import { Button, Card, Input } from "@/components/ui/Primitives";
|
||||||
import { queryEntity, getEntityTimeline, getKGContradictions, type PalaceKGTriple } from "@/lib/palace-client";
|
import { queryEntity, getEntityTimeline, getKGContradictions, type PalaceKGTriple } from "@/lib/palace-client";
|
||||||
|
|
||||||
interface KnowledgeGraphViewProps {
|
interface KnowledgeGraphViewProps {
|
||||||
@ -61,15 +61,14 @@ export function KnowledgeGraphView({ wingId }: KnowledgeGraphViewProps) {
|
|||||||
<div style={{ fontWeight: 700, fontSize: "var(--nl-text-lg)" }}>Knowledge Graph</div>
|
<div style={{ fontWeight: 700, fontSize: "var(--nl-text-lg)" }}>Knowledge Graph</div>
|
||||||
|
|
||||||
<div style={{ display: "flex", gap: "var(--nl-space-2)" }}>
|
<div style={{ display: "flex", gap: "var(--nl-space-2)" }}>
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={entity}
|
value={entity}
|
||||||
onChange={(e) => setEntity(e.target.value)}
|
onChange={(e) => setEntity(e.target.value)}
|
||||||
onKeyDown={(e) => e.key === "Enter" && handleQuery()}
|
onKeyDown={(e) => e.key === "Enter" && handleQuery()}
|
||||||
placeholder="Query entity (e.g. React, Fastify)..."
|
placeholder="Query entity (e.g. React, Fastify)..."
|
||||||
aria-label="Query knowledge graph entity"
|
aria-label="Query knowledge graph entity"
|
||||||
className="input"
|
className="flex-1"
|
||||||
style={{ flex: 1 }}
|
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleQuery} aria-label="Query entity">
|
<Button onClick={handleQuery} aria-label="Query entity">
|
||||||
Query
|
Query
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { Badge } from "@/components/ui/Primitives";
|
||||||
import { listMemories, type PalaceMemory } from "@/lib/palace-client";
|
import { listMemories, type PalaceMemory } from "@/lib/palace-client";
|
||||||
|
|
||||||
interface MemoryTimelineProps {
|
interface MemoryTimelineProps {
|
||||||
@ -57,7 +58,7 @@ export function MemoryTimeline({ wingId }: MemoryTimelineProps) {
|
|||||||
{mems.map((mem) => (
|
{mems.map((mem) => (
|
||||||
<div key={mem.id} style={{ display: "grid", gap: "var(--nl-space-1)" }}>
|
<div key={mem.id} style={{ display: "grid", gap: "var(--nl-space-1)" }}>
|
||||||
<div style={{ display: "flex", gap: "var(--nl-space-2)", alignItems: "center" }}>
|
<div style={{ display: "flex", gap: "var(--nl-space-2)", alignItems: "center" }}>
|
||||||
<span className="badge" style={{ fontSize: "0.7rem" }}>{mem.hall}</span>
|
<Badge variant="neutral" size="sm">{mem.hall}</Badge>
|
||||||
<span style={{ color: "var(--nl-text-secondary)", fontSize: "0.75rem" }}>
|
<span style={{ color: "var(--nl-text-secondary)", fontSize: "0.75rem" }}>
|
||||||
{new Date(mem.createdAt).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" })}
|
{new Date(mem.createdAt).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" })}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Button, Card } from "@/components/ui/Primitives";
|
import { Badge, Button, Card, Input } from "@/components/ui/Primitives";
|
||||||
import { searchPalace, listMemories, type PalaceMemory } from "@/lib/palace-client";
|
import { searchPalace, listMemories, type PalaceMemory } from "@/lib/palace-client";
|
||||||
|
|
||||||
interface PalacePanelProps {
|
interface PalacePanelProps {
|
||||||
@ -62,15 +62,14 @@ export function PalacePanel({ wingId }: PalacePanelProps) {
|
|||||||
<div style={{ fontWeight: 700, fontSize: "var(--nl-text-lg)" }}>Palace Memory</div>
|
<div style={{ fontWeight: 700, fontSize: "var(--nl-text-lg)" }}>Palace Memory</div>
|
||||||
|
|
||||||
<div style={{ display: "flex", gap: "var(--nl-space-2)" }}>
|
<div style={{ display: "flex", gap: "var(--nl-space-2)" }}>
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
|
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
|
||||||
placeholder="Search memories..."
|
placeholder="Search memories..."
|
||||||
aria-label="Search palace memories"
|
aria-label="Search palace memories"
|
||||||
className="input"
|
className="flex-1"
|
||||||
style={{ flex: 1 }}
|
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleSearch} aria-label="Search">
|
<Button onClick={handleSearch} aria-label="Search">
|
||||||
Search
|
Search
|
||||||
@ -99,12 +98,12 @@ export function PalacePanel({ wingId }: PalacePanelProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
<span
|
<Badge
|
||||||
className="badge"
|
variant="neutral"
|
||||||
style={{ backgroundColor: hallColor[mem.hall] ?? "var(--nl-text-secondary)", color: "var(--nl-on-accent)", fontSize: "0.75rem" }}
|
style={{ backgroundColor: hallColor[mem.hall] ?? "var(--nl-text-secondary)", color: "var(--nl-on-accent)" }}
|
||||||
>
|
>
|
||||||
{mem.hall}
|
{mem.hall}
|
||||||
</span>
|
</Badge>
|
||||||
<span style={{ color: "var(--nl-text-secondary)", fontSize: "0.75rem" }}>
|
<span style={{ color: "var(--nl-text-secondary)", fontSize: "0.75rem" }}>
|
||||||
{new Date(mem.createdAt).toLocaleDateString()}
|
{new Date(mem.createdAt).toLocaleDateString()}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user