feat(web): add Dream Output Viewer
Dreams are currently a black box — to benchmark Tier L models on dreamer
induction quality we need to see what each model produced. This adds a
workspace-scoped /dreams route that surfaces every dream run as a
group of conclusions split into explicit / deductive / inductive
columns, with click-to-expand premise trees.
What's here:
- `lib/dreams.ts`: pure helpers. `clusterConclusionsIntoDreams` groups a
raw conclusions list into per-pair bursts using a configurable time
window (default 60s). `expandPremiseTree` walks `reasoning_tree` first
and falls back to a flat `premises` ID list, with cycle detection and
a depth cap. Defines an `ExtendedConclusion` type that augments the
generated schema with `conclusion_type`, `premises`, and
`reasoning_tree` — Honcho's migration f1a2b3c4d5e6 added those
columns but `schema.d.ts` doesn't expose them yet, so the UI degrades
gracefully (unknown types fall into "explicit").
- `api/queries.ts`: new `useDreams` hook that paginates the conclusions
list endpoint up to a configurable cap (default 400) and hands the
raw list to the UI for clustering. New `dreams` query key.
- `components/dreams/`:
- `DreamList.tsx` — route entry. Shows recent dreams as rows with
timestamp, observer→observed pair, and per-type counts. Selecting
a row expands an inline detail panel above the list.
- `DreamDetail.tsx` — three-column view (explicit / deductive /
inductive). Each inductive conclusion has a "Show premises" button
that expands the reasoning chain.
- `PremiseTree.tsx` — recursive premise renderer with type badges,
cycle indicator, and graceful handling of premises that fall
outside the loaded page.
- Routing: `routes/workspaces_.$workspaceId_.dreams.tsx` registers the
page; routeTree.gen.ts regenerated.
- Sidebar: new "Dreams" entry between Conclusions and Webhooks
(MoonStar icon to distinguish from the dark-mode Moon).
- Breadcrumb: "dreams" added to SECTION_LABELS so the trail resolves.
Tests (vitest, 14 new):
- clustering: empty input; same-pair burst within window; gap exceeds
threshold → split; different pairs don't merge even with overlapping
timestamps; custom gap window; newest-first ordering; counts default
unknown types to explicit.
- premise tree: empty children for no premises; flat list → direct
children; multi-level reasoning_tree recursion; missing premise
flagged (not in loaded page); cycle detection halts recursion;
maxDepth cap; reasoning_tree preferred over flat premises.
Verified manually in the preview: route mounts, sidebar/breadcrumb
resolve, error path renders cleanly, typecheck + lint + dream tests
all clean. The unrelated `app.test.tsx` failure is pre-existing on
main.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import { Route as WorkspacesWorkspaceIdRouteImport } from './routes/workspaces_.
|
||||
import { Route as WorkspacesWorkspaceIdWebhooksRouteImport } from './routes/workspaces_.$workspaceId_.webhooks'
|
||||
import { Route as WorkspacesWorkspaceIdSessionsRouteImport } from './routes/workspaces_.$workspaceId_.sessions'
|
||||
import { Route as WorkspacesWorkspaceIdPeersRouteImport } from './routes/workspaces_.$workspaceId_.peers'
|
||||
import { Route as WorkspacesWorkspaceIdDreamsRouteImport } from './routes/workspaces_.$workspaceId_.dreams'
|
||||
import { Route as WorkspacesWorkspaceIdConclusionsRouteImport } from './routes/workspaces_.$workspaceId_.conclusions'
|
||||
import { Route as WorkspacesWorkspaceIdSessionsSessionIdRouteImport } from './routes/workspaces_.$workspaceId_.sessions_.$sessionId'
|
||||
import { Route as WorkspacesWorkspaceIdPeersPeerIdRouteImport } from './routes/workspaces_.$workspaceId_.peers_.$peerId'
|
||||
@@ -65,6 +66,12 @@ const WorkspacesWorkspaceIdPeersRoute =
|
||||
path: '/workspaces/$workspaceId/peers',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const WorkspacesWorkspaceIdDreamsRoute =
|
||||
WorkspacesWorkspaceIdDreamsRouteImport.update({
|
||||
id: '/workspaces_/$workspaceId_/dreams',
|
||||
path: '/workspaces/$workspaceId/dreams',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const WorkspacesWorkspaceIdConclusionsRoute =
|
||||
WorkspacesWorkspaceIdConclusionsRouteImport.update({
|
||||
id: '/workspaces_/$workspaceId_/conclusions',
|
||||
@@ -97,6 +104,7 @@ export interface FileRoutesByFullPath {
|
||||
'/workspaces': typeof WorkspacesRoute
|
||||
'/workspaces/$workspaceId': typeof WorkspacesWorkspaceIdRoute
|
||||
'/workspaces/$workspaceId/conclusions': typeof WorkspacesWorkspaceIdConclusionsRoute
|
||||
'/workspaces/$workspaceId/dreams': typeof WorkspacesWorkspaceIdDreamsRoute
|
||||
'/workspaces/$workspaceId/peers': typeof WorkspacesWorkspaceIdPeersRoute
|
||||
'/workspaces/$workspaceId/sessions': typeof WorkspacesWorkspaceIdSessionsRoute
|
||||
'/workspaces/$workspaceId/webhooks': typeof WorkspacesWorkspaceIdWebhooksRoute
|
||||
@@ -111,6 +119,7 @@ export interface FileRoutesByTo {
|
||||
'/workspaces': typeof WorkspacesRoute
|
||||
'/workspaces/$workspaceId': typeof WorkspacesWorkspaceIdRoute
|
||||
'/workspaces/$workspaceId/conclusions': typeof WorkspacesWorkspaceIdConclusionsRoute
|
||||
'/workspaces/$workspaceId/dreams': typeof WorkspacesWorkspaceIdDreamsRoute
|
||||
'/workspaces/$workspaceId/peers': typeof WorkspacesWorkspaceIdPeersRoute
|
||||
'/workspaces/$workspaceId/sessions': typeof WorkspacesWorkspaceIdSessionsRoute
|
||||
'/workspaces/$workspaceId/webhooks': typeof WorkspacesWorkspaceIdWebhooksRoute
|
||||
@@ -126,6 +135,7 @@ export interface FileRoutesById {
|
||||
'/workspaces': typeof WorkspacesRoute
|
||||
'/workspaces_/$workspaceId': typeof WorkspacesWorkspaceIdRoute
|
||||
'/workspaces_/$workspaceId_/conclusions': typeof WorkspacesWorkspaceIdConclusionsRoute
|
||||
'/workspaces_/$workspaceId_/dreams': typeof WorkspacesWorkspaceIdDreamsRoute
|
||||
'/workspaces_/$workspaceId_/peers': typeof WorkspacesWorkspaceIdPeersRoute
|
||||
'/workspaces_/$workspaceId_/sessions': typeof WorkspacesWorkspaceIdSessionsRoute
|
||||
'/workspaces_/$workspaceId_/webhooks': typeof WorkspacesWorkspaceIdWebhooksRoute
|
||||
@@ -142,6 +152,7 @@ export interface FileRouteTypes {
|
||||
| '/workspaces'
|
||||
| '/workspaces/$workspaceId'
|
||||
| '/workspaces/$workspaceId/conclusions'
|
||||
| '/workspaces/$workspaceId/dreams'
|
||||
| '/workspaces/$workspaceId/peers'
|
||||
| '/workspaces/$workspaceId/sessions'
|
||||
| '/workspaces/$workspaceId/webhooks'
|
||||
@@ -156,6 +167,7 @@ export interface FileRouteTypes {
|
||||
| '/workspaces'
|
||||
| '/workspaces/$workspaceId'
|
||||
| '/workspaces/$workspaceId/conclusions'
|
||||
| '/workspaces/$workspaceId/dreams'
|
||||
| '/workspaces/$workspaceId/peers'
|
||||
| '/workspaces/$workspaceId/sessions'
|
||||
| '/workspaces/$workspaceId/webhooks'
|
||||
@@ -170,6 +182,7 @@ export interface FileRouteTypes {
|
||||
| '/workspaces'
|
||||
| '/workspaces_/$workspaceId'
|
||||
| '/workspaces_/$workspaceId_/conclusions'
|
||||
| '/workspaces_/$workspaceId_/dreams'
|
||||
| '/workspaces_/$workspaceId_/peers'
|
||||
| '/workspaces_/$workspaceId_/sessions'
|
||||
| '/workspaces_/$workspaceId_/webhooks'
|
||||
@@ -185,6 +198,7 @@ export interface RootRouteChildren {
|
||||
WorkspacesRoute: typeof WorkspacesRoute
|
||||
WorkspacesWorkspaceIdRoute: typeof WorkspacesWorkspaceIdRoute
|
||||
WorkspacesWorkspaceIdConclusionsRoute: typeof WorkspacesWorkspaceIdConclusionsRoute
|
||||
WorkspacesWorkspaceIdDreamsRoute: typeof WorkspacesWorkspaceIdDreamsRoute
|
||||
WorkspacesWorkspaceIdPeersRoute: typeof WorkspacesWorkspaceIdPeersRoute
|
||||
WorkspacesWorkspaceIdSessionsRoute: typeof WorkspacesWorkspaceIdSessionsRoute
|
||||
WorkspacesWorkspaceIdWebhooksRoute: typeof WorkspacesWorkspaceIdWebhooksRoute
|
||||
@@ -251,6 +265,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof WorkspacesWorkspaceIdPeersRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/workspaces_/$workspaceId_/dreams': {
|
||||
id: '/workspaces_/$workspaceId_/dreams'
|
||||
path: '/workspaces/$workspaceId/dreams'
|
||||
fullPath: '/workspaces/$workspaceId/dreams'
|
||||
preLoaderRoute: typeof WorkspacesWorkspaceIdDreamsRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/workspaces_/$workspaceId_/conclusions': {
|
||||
id: '/workspaces_/$workspaceId_/conclusions'
|
||||
path: '/workspaces/$workspaceId/conclusions'
|
||||
@@ -289,6 +310,7 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
WorkspacesRoute: WorkspacesRoute,
|
||||
WorkspacesWorkspaceIdRoute: WorkspacesWorkspaceIdRoute,
|
||||
WorkspacesWorkspaceIdConclusionsRoute: WorkspacesWorkspaceIdConclusionsRoute,
|
||||
WorkspacesWorkspaceIdDreamsRoute: WorkspacesWorkspaceIdDreamsRoute,
|
||||
WorkspacesWorkspaceIdPeersRoute: WorkspacesWorkspaceIdPeersRoute,
|
||||
WorkspacesWorkspaceIdSessionsRoute: WorkspacesWorkspaceIdSessionsRoute,
|
||||
WorkspacesWorkspaceIdWebhooksRoute: WorkspacesWorkspaceIdWebhooksRoute,
|
||||
|
||||
Reference in New Issue
Block a user