Files
agent/.superpowers/sdd/task-2-brief.md
2026-07-20 08:02:20 +08:00

8.2 KiB

Task 2: Workbench Types And Project-Scoped Mock Data

Files:

  • Create: apps/web/src/features/workbench/types.ts
  • Create: apps/web/src/features/workbench/mockData.ts
  • Test: apps/web/src/features/workbench/mockData.test.ts

Interfaces:

  • Produces: type ChannelType = 'overview' | 'inbox' | 'tasks' | 'ai_sessions' | 'notes_sources' | 'cron' | 'custom_link'

  • Produces: getProjectWorkspace(projectID: number): ProjectWorkspace

  • Produces: workbenchProjects: WorkbenchProject[]

  • Step 1: Write failing mock data tests

Create apps/web/src/features/workbench/mockData.test.ts:

import { describe, expect, it } from 'vitest';
import { getProjectWorkspace, workbenchProjects } from './mockData';

describe('workbench mock data', () => {
  it('provides dynamic projects', () => {
    expect(workbenchProjects.length).toBeGreaterThanOrEqual(2);
    expect(workbenchProjects[0]).toMatchObject({ id: expect.any(Number), name: expect.any(String) });
  });

  it('loads project-specific channels and counts', () => {
    const first = getProjectWorkspace(workbenchProjects[0].id);
    const second = getProjectWorkspace(workbenchProjects[1].id);

    expect(first.project.id).not.toBe(second.project.id);
    expect(first.channels.map((channel) => channel.type)).toEqual([
      'overview',
      'inbox',
      'tasks',
      'ai_sessions',
      'notes_sources',
      'cron',
      'custom_link',
    ]);
    expect(first.channels.find((channel) => channel.type === 'custom_link')).toMatchObject({
      title: expect.any(String),
      url: expect.stringMatching(/^https?:\/\//),
    });
  });
});
  • Step 2: Run test to verify it fails

Run:

Set-Location D:\work\senlinai\agent\apps\web
npm test -- --run src/features/workbench/mockData.test.ts

Expected: FAIL because the files do not exist.

  • Step 3: Create types.ts

Create apps/web/src/features/workbench/types.ts:

export type ChannelType =
  | 'overview'
  | 'inbox'
  | 'tasks'
  | 'ai_sessions'
  | 'notes_sources'
  | 'cron'
  | 'custom_link';

export type WorkbenchProject = {
  id: number;
  name: string;
  description: string;
  initials: string;
  unreadCount: number;
};

export type WorkbenchChannel = {
  id: string;
  projectID: number;
  type: ChannelType;
  title: string;
  icon: string;
  count?: number;
  url?: string;
  sortOrder: number;
};

export type InboxMessage = {
  id: string;
  source: string;
  title: string;
  summary: string;
  status: 'open' | 'processed' | 'archived';
  tag: string;
  time: string;
};

export type WorkTask = {
  id: string;
  title: string;
  summary: string;
  completed: boolean;
  owner: string;
  due: string;
  tag: string;
};

export type AISessionItem = {
  id: string;
  title: string;
  summary: string;
  updatedAt: string;
  references: string[];
};

export type NoteSourceItem = {
  id: string;
  kind: 'note' | 'file' | 'link';
  title: string;
  updatedAt: string;
  tag: string;
  source: string;
};

export type CronPlan = {
  id: string;
  title: string;
  schedule: string;
  nextRun: string;
  enabled: boolean;
  lastResult: string;
  owner: string;
};

export type InspectorItem = {
  title: string;
  type: string;
  description: string;
  properties: Array<{ label: string; value: string }>;
};

export type ProjectWorkspace = {
  project: WorkbenchProject;
  channels: WorkbenchChannel[];
  tags: string[];
  recentSessions: AISessionItem[];
  inbox: InboxMessage[];
  tasks: WorkTask[];
  aiSessions: AISessionItem[];
  notesSources: NoteSourceItem[];
  cronPlans: CronPlan[];
};
  • Step 4: Create mockData.ts

Create apps/web/src/features/workbench/mockData.ts with two project workspaces:

import type { ProjectWorkspace, WorkbenchChannel, WorkbenchProject } from './types';

export const workbenchProjects: WorkbenchProject[] = [
  { id: 1, name: 'Project A1', description: 'Client strategy workspace', initials: 'A1', unreadCount: 36 },
  { id: 2, name: 'Project A2', description: 'Operations planning workspace', initials: 'A2', unreadCount: 12 },
];

function systemChannels(projectID: number): WorkbenchChannel[] {
  return [
    { id: `${projectID}-overview`, projectID, type: 'overview', title: 'Overview', icon: 'home', count: 635, sortOrder: 1 },
    { id: `${projectID}-inbox`, projectID, type: 'inbox', title: 'Message Flow', icon: 'mail', count: 36, sortOrder: 2 },
    { id: `${projectID}-tasks`, projectID, type: 'tasks', title: 'Work Plan', icon: 'list', count: 8, sortOrder: 3 },
    { id: `${projectID}-ai`, projectID, type: 'ai_sessions', title: 'AI Sessions', icon: 'sparkles', count: 4, sortOrder: 4 },
    { id: `${projectID}-notes`, projectID, type: 'notes_sources', title: 'Notes & Sources', icon: 'file', count: 343, sortOrder: 5 },
    { id: `${projectID}-cron`, projectID, type: 'cron', title: 'Cron Plans', icon: 'clock', count: 45, sortOrder: 6 },
    {
      id: `${projectID}-custom-roadmap`,
      projectID,
      type: 'custom_link',
      title: projectID === 1 ? 'Roadmap Board' : 'Ops Dashboard',
      icon: 'link',
      url: projectID === 1 ? 'https://example.com/roadmap-a1' : 'https://example.com/ops-a2',
      sortOrder: 7,
    },
  ];
}

const workspaces: ProjectWorkspace[] = workbenchProjects.map((project) => ({
  project,
  channels: systemChannels(project.id),
  tags: ['all', 'tag1', 'UI', 'knowledge'],
  recentSessions: [
    { id: `${project.id}-session-1`, title: 'Skill setup notes', summary: 'Install and verify Codex skills.', updatedAt: 'Yesterday', references: ['Notes'] },
    { id: `${project.id}-session-2`, title: 'Pricing logic analysis', summary: 'Compare decision branches and risks.', updatedAt: '7 days ago', references: ['Tasks', 'Sources'] },
  ],
  inbox: [
    { id: `${project.id}-inbox-1`, source: 'Manual', title: 'Collect competitor pricing notes', summary: 'Turn pasted research into structured follow-up tasks.', status: 'open', tag: 'UI', time: '09:32' },
    { id: `${project.id}-inbox-2`, source: 'AI', title: 'Meeting summary candidate', summary: 'Review suggested note before saving official object.', status: 'processed', tag: 'knowledge', time: 'Yesterday' },
  ],
  tasks: [
    { id: `${project.id}-task-1`, title: 'Confirm homepage information architecture', summary: 'Review channel layout and right inspector behavior.', completed: false, owner: 'David', due: 'Today', tag: 'UI' },
    { id: `${project.id}-task-2`, title: 'Archive old planning notes', summary: 'Move outdated notes into processed state.', completed: true, owner: 'Team', due: 'Yesterday', tag: 'knowledge' },
  ],
  aiSessions: [
    { id: `${project.id}-ai-1`, title: 'UI prototype critique', summary: 'Discuss Discord-like project channel behavior.', updatedAt: '10 min ago', references: ['ScreenShot.png', 'design.md'] },
    { id: `${project.id}-ai-2`, title: 'Task sharing policy', summary: 'Validate explicit object sharing rules.', updatedAt: 'Yesterday', references: ['Tasks'] },
  ],
  notesSources: [
    { id: `${project.id}-note-1`, kind: 'note', title: 'Product workbench principles', updatedAt: 'Today', tag: 'knowledge', source: 'Markdown' },
    { id: `${project.id}-file-1`, kind: 'file', title: 'ScreenShot.png', updatedAt: 'Today', tag: 'UI', source: 'Attachment' },
    { id: `${project.id}-link-1`, kind: 'link', title: 'Reference board', updatedAt: '7 days ago', tag: 'tag1', source: 'URL' },
  ],
  cronPlans: [
    { id: `${project.id}-cron-1`, title: 'Weekly inbox review reminder', schedule: '0 9 * * 1', nextRun: 'Next Monday 09:00', enabled: true, lastResult: 'Not run yet', owner: 'David' },
    { id: `${project.id}-cron-2`, title: 'Monthly source cleanup', schedule: '0 10 1 * *', nextRun: 'Next month', enabled: false, lastResult: 'Paused', owner: 'Team' },
  ],
}));

export function getProjectWorkspace(projectID: number): ProjectWorkspace {
  return workspaces.find((workspace) => workspace.project.id === projectID) ?? workspaces[0];
}
  • Step 5: Run test

Run:

Set-Location D:\work\senlinai\agent\apps\web
npm test -- --run src/features/workbench/mockData.test.ts

Expected: PASS.

  • Step 6: Commit

Run:

Set-Location D:\work\senlinai\agent
git add apps\web\src\features\workbench\types.ts apps\web\src\features\workbench\mockData.ts apps\web\src\features\workbench\mockData.test.ts
git commit -m "feat: add project workspace data model"