Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7508bda
fix(dashboard): find workspaces by task in command palette
ScriptedAlchemy Nov 8, 2026
2d1d139
fix(dashboard): prioritize workspace names in palette search
ScriptedAlchemy Nov 8, 2026
7f5753e
fix(dashboard): preserve keyboard selection across late registry matches
ScriptedAlchemy Nov 8, 2026
d9288b2
fix(dashboard): scope register to consumed channel fields
ScriptedAlchemy Nov 8, 2026
dccdaf9
fix(dashboard): keep selected view on palette reselection
ScriptedAlchemy Nov 8, 2026
feeef18
test(dashboard): assert scoped palette project navigation
ScriptedAlchemy Nov 8, 2026
a66ccf6
fix(dashboard): retain palette selection across registry reorder
ScriptedAlchemy Nov 8, 2026
5d71622
fix(dashboard): preserve Brain index deep links from palette
ScriptedAlchemy Nov 8, 2026
ece64fc
fix(dashboard): reset palette selection on query edits
ScriptedAlchemy Nov 8, 2026
7dbe8ff
fix(dashboard): preserve scoped views across label reconciliation
ScriptedAlchemy Nov 8, 2026
a7eb649
fix(dashboard): retain workspace deep links on palette selection
ScriptedAlchemy Nov 8, 2026
f3c0700
fix(dashboard): retain views with orphaned scope labels
ScriptedAlchemy Nov 8, 2026
9df52b3
test(automation): bind scheduler fixtures to backend identities
ScriptedAlchemy Nov 8, 2026
43f4ef6
fix(sessions): serve live projection during historical ingest
ScriptedAlchemy Nov 8, 2026
f81ee08
fix(lcm): serialize summary publication per store and session
ScriptedAlchemy Nov 8, 2026
ef851d8
test(mcp): receive connected host replies before closing stdin
ScriptedAlchemy Nov 8, 2026
6b76f00
fix(sessions): reserve history preemption for waiting readers
ScriptedAlchemy Nov 8, 2026
dafd463
test(sessions): yield refresh history gate instead of blocking worker
ScriptedAlchemy Nov 8, 2026
17bf381
test(sessions): join each superseded publication independently
ScriptedAlchemy Nov 9, 2026
69101f6
test(mcp): receive connected host replies before closing stdin
ScriptedAlchemy Nov 8, 2026
0c72f7b
chore(ci): integrate the session and MCP test prerequisites
ScriptedAlchemy Nov 9, 2026
b9d135e
Merge codex/live-projection-during-history into codex/mcp-connected-r…
ScriptedAlchemy Nov 9, 2026
d9f7499
Merge codex/mcp-connected-replies into fix/4220-palette-task-discovery
ScriptedAlchemy Nov 9, 2026
b85c686
fix(dashboard): expose unqualified session links to Loom
ScriptedAlchemy Nov 9, 2026
9949ead
fix(dashboard): disambiguate provider-less Loom session links
ScriptedAlchemy Nov 9, 2026
ac1c102
test(loom): preserve bare session identity across dashboard pivots
ScriptedAlchemy Nov 9, 2026
9fab67a
test(loom): quote provider-unknown pivot regression title
ScriptedAlchemy Nov 9, 2026
622531c
test(dashboard): distinguish absent from unknown provider lanes
ScriptedAlchemy Nov 9, 2026
7e74baf
refactor(loom): remove legacy session parameter fallback
ScriptedAlchemy Nov 10, 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
4 changes: 3 additions & 1 deletion crates/tracedecay/tests/mcp_suite/serve_harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,10 @@ pub fn run_serve_after_ready(
"params": initialize_params
}));
session.await_project_ready();
// This is a connected host journey. Let each operation deliver its reply
// before timing the proxy's exit on EOF.
for request in requests {
session.send(request);
session.request(request.clone());
}
session.finish(SERVE_CHILD_TIMEOUT)
}
Expand Down
36 changes: 35 additions & 1 deletion dashboard/src/app/channels.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { describe, expect, it } from 'vitest';
import { channelForPathname, channelNumber } from './channels.ts';
import {
CHANNELS,
channelForPathname,
channelNumber,
paletteMatchesQuery,
workspaceSearchText,
} from './channels.ts';

describe('channelNumber', () => {
it('never invents a channel for a path it does not have', () => {
Expand All @@ -26,3 +32,31 @@ describe('channelForPathname', () => {
expect(channelForPathname('/tools')).toBeNull();
});
});

describe('command-palette task discovery', () => {
const matches = (query: string) =>
CHANNELS.filter((channel) =>
paletteMatchesQuery(workspaceSearchText(channel), query),
).map((channel) => channel.path);

it('finds task destinations when their workspace names are unknown', () => {
expect(matches('temporal replay')).toContain('loom');
expect(matches('transcripts')).toContain('sessions');
expect(matches('source symbols')).toContain('code');
expect(matches(' PR REVIEWS ')).toContain('delivery');
expect(matches('task board')).toContain('work');
expect(matches('scheduled jobs')).toContain('automations');
});

it('keeps existing workspace-name and project-name matches', () => {
expect(matches('code')).toContain('code');
expect(matches('code')).toContain('explorer');
expect(paletteMatchesQuery('garden project', ' GARDEN ')).toBe(true);
expect(paletteMatchesQuery('garden project', 'temporal')).toBe(false);
});

it('preserves every existing route on empty input and does not invent destinations', () => {
expect(matches('')).toEqual(CHANNELS.map((channel) => channel.path));
expect(matches('not-a-real-destination')).toEqual([]);
});
});
103 changes: 89 additions & 14 deletions dashboard/src/app/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,100 @@
export interface Channel {
readonly path: string;
readonly label: string;
/** Visible task hint, also searchable from the command palette. */
readonly taskHint: string;
}

export const CHANNELS: readonly Channel[] = [
{ path: 'brain', label: 'Brain' },
{ path: 'explorer', label: 'Explorer' },
{ path: 'loom', label: 'Loom' },
{ path: 'sessions', label: 'Sessions' },
{ path: 'agents', label: 'Agents' },
{ path: 'code', label: 'Code' },
{ path: 'knowledge', label: 'Knowledge' },
{ path: 'delivery', label: 'Delivery' },
{ path: 'automations', label: 'Automations' },
{ path: 'observatory', label: 'Observatory' },
{ path: 'costs', label: 'Costs' },
{ path: 'settings', label: 'Settings' },
{ path: 'work', label: 'Work' },
{ path: 'workflows', label: 'Workflows' },
{
path: 'brain',
label: 'Brain',
taskHint: 'Project health, scope and relationships',
},
{
path: 'explorer',
label: 'Explorer',
taskHint: 'Search code, sessions and knowledge together',
},
{
path: 'loom',
label: 'Loom',
taskHint: 'Agent timelines and temporal replay',
},
{
path: 'sessions',
label: 'Sessions',
taskHint: 'Session transcripts and history',
},
{
path: 'agents',
label: 'Agents',
taskHint: 'Agent identities and activity',
},
{
path: 'code',
label: 'Code',
taskHint: 'Source files, symbols and dependencies',
},
{
path: 'knowledge',
label: 'Knowledge',
taskHint: 'Memory facts and citations',
},
{
path: 'delivery',
label: 'Delivery',
taskHint: 'PR reviews, CI checks and releases',
},
{
path: 'automations',
label: 'Automations',
taskHint: 'Scheduled jobs, skills and runs',
},
{
path: 'observatory',
label: 'Observatory',
taskHint: 'Diagnostics, storage health and telemetry',
},
{
path: 'costs',
label: 'Costs',
taskHint: 'Token usage, savings and spending',
},
{
path: 'settings',
label: 'Settings',
taskHint: 'Configuration, integrations and permissions',
},
{
path: 'work',
label: 'Work',
taskHint: 'Task board, plans and progress',
},
{
path: 'workflows',
label: 'Workflows',
taskHint: 'Workflow definitions, execution and runs',
},
] as const;

/** Search the task described by a workspace, rather than only its proper name.
* The rail continues to use the established label and route unchanged. */
export function workspaceSearchText(channel: Channel): string {
return `${channel.label} ${channel.taskHint}`.toLowerCase();
}

/** Match all typed words, regardless of whitespace/case or the order in which
* the workspace's label and task description supply them. Empty queries show
* every available entry, including registered projects. */
export function paletteMatchesQuery(searchText: string, query: string): boolean {
return query
.trim()
.toLowerCase()
.split(/\s+/)
.every((term) => searchText.includes(term));
}

/** Zero-padded channel number for a workspace path (`code` → `06`). Unknown
* paths get `--`: the instrument never invents a channel it does not have. */
export function channelNumber(path: string): string {
Expand Down
Loading
Loading