Skip to content
Open

Dev #3566

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
e41e0e2
add plans for student workspace and commercial-licensing readiness
rexong Jun 15, 2026
06c81c6
auto-claude: subtask-1-1 - Create packages/contracts/src/students.ts …
rexong Jun 15, 2026
d4c0f91
auto-claude: subtask-1-2 - Extend DesktopBridge interface in packages…
rexong Jun 15, 2026
e777394
auto-claude: subtask-2-1 - Add studentRegistryPath to DesktopEnvironment
rexong Jun 15, 2026
97742fa
auto-claude: subtask-2-2 - Create DesktopStudents service for student…
rexong Jun 15, 2026
d90feda
auto-claude: subtask-3-1 - Add IPC channels and methods for student r…
rexong Jun 15, 2026
4a6318d
auto-claude: subtask-3-2 - Register student IPC handlers in apps/desk…
rexong Jun 15, 2026
459b70e
auto-claude: subtask-4-1 - Add getStudents and setStudents to the per…
rexong Jun 15, 2026
9ee3128
auto-claude: subtask-5-1 - Create students route with two-pane layout
rexong Jun 15, 2026
2fea320
auto-claude: subtask-5-2 - Create apps/web/src/components/students/St…
rexong Jun 15, 2026
ec48510
auto-claude: subtask-6-1 - Create the three field sub-components: (1)…
rexong Jun 15, 2026
c9fccf7
auto-claude: subtask-6-2 - Create apps/web/src/components/students/St…
rexong Jun 15, 2026
dbf6165
auto-claude: subtask-7-1 - Create apps/web/src/components/students/li…
rexong Jun 15, 2026
296f168
auto-claude: subtask-7-2 - Create StudentDetail component
rexong Jun 15, 2026
de07ba4
auto-claude: subtask-8-1 - Add a 'Students' navigation entry in the S…
rexong Jun 15, 2026
fe9dc93
auto-claude: subtask-9-1 - Create packages/contracts/src/students.tes…
rexong Jun 15, 2026
bf4c896
auto-claude: subtask-9-2 - Create apps/desktop/src/settings/DesktopSt…
rexong Jun 15, 2026
092ac45
auto-claude: subtask-10-1 - Wire all student components together in t…
rexong Jun 15, 2026
2865185
auto-claude: fix TypeScript error in students.test.ts - add optional …
rexong Jun 15, 2026
7a69f2a
chore: add auto-claude entries to .gitignore
rexong Jun 16, 2026
83932a8
Merge branch 'pingdotgg:main' into dev
tutoratlas Jun 16, 2026
8beb55d
fix: Fix phone country field to store ISO codes instead of dial codes…
rexong Jun 17, 2026
eee7d71
chore: add auto-claude entries to .gitignore
rexong Jun 15, 2026
54d2c87
write plan
rexong Jun 17, 2026
6dd9d59
chore: add docs and complete manual test for task 22
rexong Jun 17, 2026
7d13a2e
Merge branch 'pingdotgg:main' into dev
tutoratlas Jun 17, 2026
4914baa
Merge branch 'pingdotgg:main' into auto-claude/001-implement-student-…
tutoratlas Jun 17, 2026
73e19ed
add plans for TUT-18 and TUT-17
rexong Jun 17, 2026
12ec57d
auto-claude: subtask-1-1 - Create Student schema with slug derivation…
rexong Jun 17, 2026
0a23820
auto-claude: subtask-1-2 - Add three new method signatures to the Des…
rexong Jun 17, 2026
866720b
auto-claude: subtask-2-1 - Add openPath method to ElectronShellShape …
rexong Jun 17, 2026
ec53a79
auto-claude: subtask-2-2 - Create print.css A4 stylesheet
rexong Jun 17, 2026
3cb3f0b
auto-claude: subtask-2-3 - Create DesktopWorkspace Effect service
rexong Jun 17, 2026
5227bb2
auto-claude: subtask-2-4 - Create DesktopPdfRenderer Effect service
rexong Jun 17, 2026
d7dba5a
auto-claude: subtask-3-1 - Add three new channel constants to channel…
rexong Jun 17, 2026
4146383
auto-claude: subtask-3-2 - Create apps/desktop/src/ipc/methods/pdf.ts…
rexong Jun 17, 2026
92ca92d
auto-claude: subtask-3-3 - Create apps/desktop/src/ipc/methods/worksp…
rexong Jun 17, 2026
591f139
auto-claude: subtask-3-4 - Register new IPC handlers in DesktopIpcHan…
rexong Jun 17, 2026
a245507
auto-claude: subtask-3-5 - Expose three new methods on desktopBridge …
rexong Jun 17, 2026
a7f2b17
auto-claude: subtask-3-6 - Merge DesktopPdfRenderer.layer and Desktop…
rexong Jun 17, 2026
8313d9c
auto-claude: subtask-4-1 - Create markdownToHtml pure function for PD…
rexong Jun 17, 2026
6232aad
auto-claude: subtask-4-2 - Create apps/web/src/pdf/renderPdf.ts orche…
rexong Jun 17, 2026
18b54a4
auto-claude: subtask-4-3 - Add bridge wrappers to localApi.ts for the…
rexong Jun 17, 2026
29c7f43
auto-claude: subtask-5-1 - Create StudentDetail component
rexong Jun 17, 2026
b48d745
auto-claude: subtask-5-2 - Create apps/web/src/components/pdf/RenderP…
rexong Jun 17, 2026
1ec6381
auto-claude: subtask-5-3 - Part C: Hide PR-status icon and git-branch…
rexong Jun 17, 2026
66e3041
auto-claude: subtask-6-1 - Full typecheck across all packages to veri…
rexong Jun 17, 2026
ac36a4b
auto-claude: subtask-6-2 - End-to-end verification of materials works…
rexong Jun 17, 2026
3673133
Merge pull request #1 from tutoratlas/auto-claude/001-implement-stude…
tutoratlas Jun 18, 2026
e9add25
Merge auto-claude/002 (materials workspace + PDF) into dev
rexong Jun 19, 2026
2dd1ee6
Add TutorAtlas rebuild plan and brand source art
rexong Jun 26, 2026
a9e077e
Enrich unified-workspace plan (03) with concrete change-map
rexong Jun 26, 2026
9ee2335
auto-claude: subtask-1-1 - Create apps/web/src/pdf/fonts.ts — font em…
rexong Jun 26, 2026
df3059a
auto-claude: subtask-1-2 - Create apps/web/src/pdf/print.css — extrac…
rexong Jun 26, 2026
e5f63c0
auto-claude: subtask-1-1 - Replace 'T3 Code' with 'TutorAtlas' in des…
rexong Jun 26, 2026
4e394a2
auto-claude: subtask-1-3 - Update apps/web/src/pdf/markdownToHtml.ts …
rexong Jun 26, 2026
1ab11bd
auto-claude: subtask-2-1 - Replace 'T3 Code' with 'TutorAtlas' in web…
rexong Jun 26, 2026
756da84
auto-claude: subtask-2-1 - Mount RenderPdfButton in FilePreviewPanel.…
rexong Jun 26, 2026
ef17992
auto-claude: subtask-2-2 - Delete dead duplicate print.css file
rexong Jun 26, 2026
fa7076c
auto-claude: subtask-2-2 - Update apps/web/src/branding.test.ts to ex…
rexong Jun 26, 2026
f8d8625
auto-claude: subtask-3-1 - Update scripts/build-desktop-artifact.ts: …
rexong Jun 26, 2026
f67e70f
auto-claude: subtask-3-2 - Update scripts/build-desktop-artifact.test…
rexong Jun 26, 2026
8a4913f
auto-claude: subtask-4-1 - Create scripts/gen-brand-assets.mjs — the …
rexong Jun 26, 2026
c8b14ae
auto-claude: subtask-4-2 - Wire brand assets generator script to pack…
rexong Jun 26, 2026
20dbb93
auto-claude: subtask-3-2 - End-to-end verification of the complete PD…
rexong Jun 26, 2026
bf4a8fd
auto-claude: subtask-6-1 - Complete integration verification and fix …
rexong Jun 26, 2026
0ebbc18
fix(branding): replace stale "T3 Code" sidebar wordmark with TutorAtlas
rexong Jun 27, 2026
5059eb2
Merge pull request #3 from tutoratlas/auto-claude/006-implement-brand…
tutoratlas Jun 27, 2026
7dbfd81
Merge pull request #4 from tutoratlas/auto-claude/007-implement-rende…
tutoratlas Jun 27, 2026
b98e0f8
Enrich students-by-chat plan (04) with audited wiring + Q&A decisions
rexong Jun 26, 2026
d103cef
auto-claude: subtask-1-1 - Create sanitizeStudentSlug utility: lowerc…
rexong Jun 27, 2026
bd69bcc
auto-claude: subtask-1-2 - Create unit tests for sanitizeStudentSlug …
rexong Jun 27, 2026
85d6adf
auto-claude: subtask-2-1 - Desktop environment workspace configuration
rexong Jun 27, 2026
0694d03
auto-claude: subtask-2-2 - Add T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD…
rexong Jun 27, 2026
5f8814c
auto-claude: subtask-3-1 - Use environment.workspaceRoot in DesktopWo…
rexong Jun 27, 2026
8e3bbf6
auto-claude: subtask-3-2 - Create bootstrapAtlasSkills.ts: an Effect …
rexong Jun 27, 2026
3cb59dc
auto-claude: subtask-3-3 - Wire sanitizeStudentSlug in workspace IPC …
rexong Jun 27, 2026
34be961
auto-claude: subtask-4-1 - Guard out action:add-project in CommandPal…
rexong Jun 27, 2026
1c6aa68
auto-claude: subtask-4-2 - Neutralize three project picker surfaces i…
rexong Jun 27, 2026
6ffba8b
auto-claude: subtask-1-1 - Rename T3CODE_HOME to TUTORATLAS_HOME and …
rexong Jun 27, 2026
6b7a468
auto-claude: subtask-1-2 - Rename T3CODE_HOME to TUTORATLAS_HOME and …
rexong Jun 27, 2026
1296dfd
auto-claude: subtask-1-3 - Rename T3CODE_HOME to TUTORATLAS_HOME and …
rexong Jun 27, 2026
0a05add
auto-claude: subtask-1-4 - Update .gitignore entry from .t3 to .tutor…
rexong Jun 27, 2026
591a813
auto-claude: subtask-2-1 - Update desktop test files (batch 1): Deskt…
rexong Jun 27, 2026
dff6ccd
auto-claude: subtask-2-2 - Update desktop test files (batch 2): Deskt…
rexong Jun 27, 2026
b99e741
auto-claude: subtask-2-3 - Update desktop backend test files and serv…
rexong Jun 27, 2026
54c8079
auto-claude: subtask-2-4 - Fix test assertions for app name and works…
rexong Jun 27, 2026
0e4ec2d
auto-claude: subtask-3-1 - Update docs/operations/observability.md: r…
rexong Jun 27, 2026
b85b7a7
auto-claude: subtask-3-2 - Document R6 clean-slate wipe procedure. Ad…
rexong Jun 27, 2026
a7c0c83
auto-claude: subtask-4-1 - Update student workspace schemas and inter…
rexong Jun 27, 2026
0556058
auto-claude: subtask-5-1 - Add DELETE_STUDENT_WORKSPACE_CHANNEL const…
rexong Jun 27, 2026
996b66e
auto-claude: subtask-5-2 - Update DesktopWorkspace service: add delet…
rexong Jun 27, 2026
904f428
auto-claude: subtask-5-3 - Update ensureStudentWorkspace IPC handler …
rexong Jun 27, 2026
991a895
auto-claude: subtask-5-4 - Add deleteStudentWorkspace preload binding…
rexong Jun 27, 2026
1fd27cc
auto-claude: subtask-6-1 - R3: Sidebar cleanup - rename Projects to W…
rexong Jun 27, 2026
4798696
auto-claude: subtask-6-2 - R5: Remove project-related UI from Command…
rexong Jun 27, 2026
e8a79c9
auto-claude: subtask-7-1 - Update localApi.ts: add deleteStudentWorks…
rexong Jun 27, 2026
296bc19
auto-claude: subtask-7-2 - Remove 'Generate materials' button and han…
rexong Jun 27, 2026
d8d58eb
auto-claude: subtask-7-3 - Update students.tsx route: In handleSave (…
rexong Jun 27, 2026
c374e85
auto-claude: subtask-8-1 - Fix typecheck errors from R3/R5 UI removal…
rexong Jun 27, 2026
a9e22cb
Merge pull request #5 from tutoratlas/auto-claude/009-i-need-to-see-t…
tutoratlas Jun 28, 2026
7097bf1
fix(desktop/pdf): detach load listeners on timeout via Effect.callback
rexong Jun 28, 2026
337bb04
Merge pull request #7 from tutoratlas/auto-claude/008-implement-unifi…
tutoratlas Jun 28, 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
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ build/
.logs/
release/
release-mock/
.t3
.tutoratlas
.idea/
apps/web/.playwright
apps/web/playwright-report
Expand All @@ -32,3 +32,14 @@ node_modules/
*.log
.env*
!.env.example

# Auto Claude data directory
.auto-claude/

# Auto Claude generated files
.auto-claude-security.json
.auto-claude-status
.claude_settings.json
.worktrees/
.security-key
logs/security/
82 changes: 82 additions & 0 deletions .plans/21-commercial-licensing-readiness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Commercial Licensing Readiness — TODOs Before Shipping

> **Not legal advice.** This is an engineering checklist derived from auditing the
> repo's `LICENSE`, the vendored code in `.repos/`, all declared dependencies, and
> `assets/`. Have counsel confirm before commercial release.

## Summary

The codebase is a fork of **T3 Code** (© T3 Tools Inc, **MIT**). MIT permits commercial
use, modification, and resale **provided the copyright notice + license text are
retained**. The dependency tree is overwhelmingly permissive (MIT / Apache-2.0 / BSD /
ISC) with **no copyleft (GPL/AGPL/LGPL/SSPL) and no non-commercial licenses**.

Three categories of work are required before a commercial ship: **(1) rebrand**,
**(2) third-party terms & paid-service review**, **(3) attribution hygiene**.

---

## 1. Rebrand — remove T3 trademark/brand assets (REQUIRED)

MIT grants *copyright*, **not trademark**. The "T3 Code" / "T3 Tools" name and logos
cannot ship under our product.

- [ ] Replace/remove brand assets in `assets/prod/`, `assets/dev/`, `assets/nightly/`
(`logo.svg`, `t3-black-*` favicons/icons, `black-*` / `blueprint-*` app icons).
- [ ] Find & replace hardcoded "T3 Code" / "T3 Tools" / "t3" product strings across the
codebase (app titles, window/menu labels, `package.json` names like `@t3tools/*`,
installer/winget/brew/AUR identifiers in `README.md`, update-server URLs).
- [ ] Replace product URLs/domains and Discord/support links.
- [ ] Confirm desktop app metadata (electron-builder appId, product name, publisher) is
rebranded so releases don't ship as "T3 Code".

## 2. Third-party terms & paid services (REQUIRED REVIEW)

- [ ] **`@anthropic-ai/claude-agent-sdk`** — the only *proprietary*-licensed dep
(`SEE LICENSE IN README`). Governed by **Anthropic Commercial ToS**; requires paid
API/model access. Confirm our use complies and budget for metered API cost.
- [ ] **Bundled coding-agent CLIs** (Codex, Claude Code, Cursor, OpenCode) — the app is a
GUI wrapper. Each has its **own ToS** that end-users must satisfy. Decide what we
bundle vs. require users to install/authenticate themselves, and document it.
- [ ] **Clerk (`@clerk/*`)** — SDK is MIT (fine to embed), but the **hosted service
requires a paid plan beyond the free tier** at commercial scale. Confirm plan/pricing
or swap auth provider.
- [ ] Confirm we use base **`uniwind`** (MIT) only — **NOT** the separate proprietary
`uniwind-pro` paid package.

## 3. Attribution & license hygiene (REQUIRED)

- [ ] **Retain** the existing MIT `LICENSE` (© T3 Tools Inc) and its copyright notice in
distributed artifacts — required by MIT even after rebranding.
- [ ] Generate a `NOTICE` / third-party-licenses file aggregating dependency licenses
(MIT/Apache-2.0/BSD/ISC) and ship it with the app (e.g. `license-checker` /
`oss-attribution-generator` against the installed tree).
- [ ] Apache-2.0 deps (e.g. `@pierre/diffs`, Playwright, vendored `.repos/alchemy-effect`)
require preserving any `NOTICE` content and the license text.
- [ ] Vendored reference code in `.repos/` (`effect-smol` MIT, `alchemy-effect`
Apache-2.0): confirm these are dev-only references and **not bundled** into shipped
artifacts; if bundled, include their LICENSE files.
- [ ] Fonts (DM Sans, JetBrains Mono via fontsource) are **SIL OFL** — commercial-OK, but
include the OFL text if font files are redistributed.

## 4. Final verification (REQUIRED)

- [ ] Run a full dependency license scan against the **installed** tree (no `node_modules`
present at audit time — declared-deps analysis only). Confirm zero
GPL/AGPL/LGPL/SSPL/non-commercial licenses reach the shipped bundle.
- [ ] Legal sign-off on the above before public/commercial release.

---

## License facts (reference)

| Component | License | Commercial | Note |
|---|---|---|---|
| This repo (T3 Code) | MIT (© T3 Tools Inc) | ✅ | Retain notice; trademark NOT granted |
| `.repos/effect-smol` | MIT | ✅ | Reference checkout |
| `.repos/alchemy-effect` | Apache-2.0 | ✅ | Preserve NOTICE |
| Dependencies (135 declared) | MIT / Apache-2.0 / BSD / ISC | ✅ | No copyleft found |
| `@anthropic-ai/claude-agent-sdk` | Proprietary | ⚠️ | Anthropic Commercial ToS + paid API |
| Clerk service | MIT SDK / SaaS | ⚠️ | Paid plan at scale |
| `uniwind` (base) | MIT | ✅ | Avoid proprietary `uniwind-pro` |
| Fonts (DM Sans, JetBrains Mono) | SIL OFL | ✅ | Include OFL if redistributed |
225 changes: 225 additions & 0 deletions .plans/22-1-students-via-chat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
# 22-1 — Students via Chat (Natural-language CRUD through `/student`)

## Goal

Let a tutor manage their students by talking to the AI in the existing chat
instead of (only) filling out the form. Typing `/student` primes the agent and
turns on a **student toolkit** the agent can call to list, look up, create,
update, and delete students from natural language ("add Mary, P5, takes math and
science, mum is Jenny +65 9123 4567").

The form from plan 22 **stays** — this is an additive, faster on-ramp, not a
replacement.

## Relationship to plan 22 and the iteration map

This is a sub-iteration of [22 — Student Workspace](./22-student-workspace.md).
Plan 22 ("Iteration B") built the isolated Students module: contracts, desktop
persistence (`students.json`), the browser `localStorage` fallback, IPC, the
`/students` route, and the form.

22-1 pulls **part of Iteration C forward**: the chat becomes able to *act on*
student data through tools. It deliberately does **not** yet inject a selected
student's context into coding prompts or add a "Chat about this student" button
(still C). It only wires the agent ↔ student-store action seam.

Operative constraint, same as plan 22: **ship fast**, reuse proven patterns
(the existing MCP toolkit + broker), keep the surface isolated.

## Decisions log (resolved during requirements grilling)

| # | Decision | Choice |
|---|----------|--------|
| 1 | How the AI mutates students | **Student MCP toolkit** on the existing coding agent. The agent "sees the CRUD commands" = MCP tools. Reuses `apps/server/src/mcp` infra. |
| 2 | Provider scope | **All MCP-capable providers.** A single toolkit registration is automatically visible to **Claude, Codex, and Cursor** (all three already receive MCP config). **OpenCode has no MCP** → hide/disable `/student` there. |
| 3 | Store access seam | **Broker → client → `localApi`.** The MCP handler calls back into the running app (preview-toolkit broker pattern); the client performs the read/write through the same `localApi.persistence.getStudents/setStudents` the Students page uses. Works in **desktop and web**, single persistence path, no dual-writer race, enables live UI refresh. |
| 4 | CRUD scope | **Full CRUD**: `listStudents`/`findStudents`, `getStudent`, `createStudent`, `updateStudent`, `deleteStudent`. |
| 5 | Write safety | **Confirm destructive only.** `create` auto-applies (recoverable). `update` and `delete` pause for confirmation via the existing tool-approval round-trip before persisting. |
| 6 | `/student` UX | **Primer in the main chat.** `/student` is a slash command (like `/model`, `/plan`) that inserts a short primer and enables the student tools for that turn. Tools stay **dormant** until `/student` is used. Conversation lives in the normal chat thread. |
| 7 | Phone country representation | **Standardize on `SG`/`MY`/`CN`.** Tools use the canonical code; **fix `PhoneField`** in plan 22's form (it currently stores the dial code `"+65"` in the `country` field, which breaks `links.ts` deep links). |
| 8 | Disambiguation | Tools key off **`StudentId`**. `findStudents`/`listStudents` return id + identifying fields; the agent disambiguates duplicate names by asking the user. |
| 9 | Validation enforcement | **Extract plan 22's form validation/normalization into a shared pure module**, reused by the form AND the tool write path, so the agent cannot bypass `name`-required, postal-code-6-digits-when-partial, empty-parent-drop, subject splitting. |
| 10 | Capability gating | Add a **`"students"` MCP capability** mirroring the existing `"preview"` capability; enabled for the thread when `/student` is invoked. |

## Architecture & data flow

The agent never touches the student store directly. It calls an MCP tool; the
tool's handler (in `apps/server`) brokers a request to the running client; the
client applies it through the existing `localApi` persistence path (the same one
the Students page uses), enforces validation, and returns the result.

```
User types "/student add Mary, P5, math…" in the chat composer
│ (slash command injects a primer + flags the students capability)
thread.turn.start → coding agent (Claude / Codex / Cursor)
│ agent decides to call a student tool
HTTP MCP server (apps/server/src/mcp) → Students toolkit handler
│ broker.invoke({ op: "create", input }) (create: apply)
│ update/delete → existing approval round-trip → broker.invoke(...)
StudentsBroker → WebSocket → renderer (client handler)
│ validate + normalize (shared module)
localApi.persistence.getStudents() / setStudents(list)
desktop: desktopBridge → DesktopStudents → students.json
web: localStorage (t3code:student-registry:v1)
└─► returns result to handler → agent reports what changed;
Students page refreshes live.
```

Why broker (not direct file access from the server): the MCP server is a
**separate child process** (`apps/desktop/src/backend/DesktopBackendManager.ts`)
and the store lives in the desktop main process / browser `localStorage`. The
broker keeps **one** persistence path, works in web mode (where there is no
`students.json`), and avoids last-write-wins races with the form.

## What already exists (no new infra needed)

- **MCP server + toolkit pattern** — `apps/server/src/mcp/McpHttpServer.ts`
registers toolkits; `apps/server/src/mcp/toolkits/preview/{tools,handlers}.ts`
is the template (`Tool.make` + `Toolkit.make` + handler layer).
- **Broker pattern** — `apps/server/src/mcp/PreviewAutomationBroker.ts` +
`McpInvocationContext` route a tool request to the client and await a response.
The new `StudentsBroker` mirrors it.
- **MCP delivered to every provider** — no per-provider work:
Claude `ClaudeAdapter.ts:3449` (SDK `mcpServers`), Codex `CodexAdapter.ts:1389`
(CLI `-c mcp_servers.t3-code.*` + `T3_MCP_BEARER_TOKEN`), Cursor
`CursorAdapter.ts:534` (`mcpServers` array). Credentials issued once in
`ProviderService.ts:~217` via `McpSessionRegistry`.
- **Capability flag** — `McpSessionRegistry` already carries capabilities
(currently `"preview"`); add `"students"`.
- **Slash command surface** — `apps/web/src/components/chat/ChatComposer.tsx`
(~935–990) has built-in commands (`/model`, `/plan`, `/default`) and provider
commands (`ServerProviderSlashCommand`, `packages/contracts/src/server.ts:76`);
selecting one inserts text into the composer (~1595).
- **Client persistence API** — `apps/web/src/localApi.ts` already exposes
`persistence.getStudents` / `setStudents` (built in plan 22).
- **Tool approval round-trip** — `ThreadApprovalRespondCommand` /
`thread.approval.respond` (`apps/server/src/orchestration/decider.ts:~486`,
`packages/contracts/src/orchestration.ts`). Used for `update`/`delete`.

## Work breakdown (file-by-file)

### 1. Shared validation/normalization (prereq for clean reuse)
- **New** `apps/web/src/components/students/studentFormLogic.ts` — extract the
pure functions currently inline in `StudentForm.tsx`:
`phoneValueToContract`, `addressValueToContract`, `parentsToContract`
(empty-parent drop), postal-code validation, subject splitting, `name`
required. Export a single `normalizeAndValidateStudentInput(raw)` →
`{ student } | { errors }`.
- **Edit** `apps/web/src/components/students/StudentForm.tsx` — call the shared
module instead of its private copies (no behavior change).

### 2. Phone country fix (decision 7)
- **Edit** `apps/web/src/components/students/PhoneField.tsx` — make the `<Select>`
`value` the country **code** (`"SG"`/`"MY"/"CN"`, default `"SG"`) and render the
dial code as the label. `StudentForm`'s `phoneValueFrom/ToContract` already map
`countryCode ↔ country`, so contracts then store `"SG"`; `StudentDetail` and
`links.ts` (which expect `"SG"`) start working.
- **Note** a one-line tolerance for any already-saved `"+65"` values (map dial
code → code on read) so existing data isn't broken.

> Status: decision 7 was applied ahead of this plan (PhoneField + contract
> `CountryCode` union + `StudentDetail` display were fixed, and a
> `links.test.ts` regression guard added). The read-tolerance was intentionally
> **not** added. Treat §1 (shared module) as the remaining prerequisite.

### 3. Student tool input/output contracts
- **New** `packages/contracts/src/studentTools.ts` (or extend
`students.ts`) — `effect/Schema` request/response shapes for each op
(`create`/`update`/`delete`/`get`/`list`/`find`), mirroring `Student`. Country
is `Schema.Literal("SG","MY","CN")` (now available as `CountryCode`).

### 4. Server — Students MCP toolkit
- **New** `apps/server/src/mcp/toolkits/students/tools.ts` — `Tool.make` for
`listStudents`, `findStudents`, `getStudent`, `createStudent`,
`updateStudent`, `deleteStudent`. Descriptions tell the agent: key off
`StudentId`; ask the user to disambiguate duplicate names; `update`/`delete`
require confirmation.
- **New** `apps/server/src/mcp/toolkits/students/handlers.ts` — each handler
calls `StudentsBroker.invoke({ op, input })`. `update`/`delete` first go
through the approval round-trip; on rejection, return a "cancelled" tool result.
- **Edit** `apps/server/src/mcp/McpHttpServer.ts` — register the toolkit
(mirror `PreviewToolkitRegistrationLive`), gated by the `"students"`
capability.

### 5. Server — Students broker
- **New** `apps/server/src/mcp/StudentsBroker.ts` — mirror
`PreviewAutomationBroker`: `invoke(request)` routes to the client over WS and
awaits the response. Reuse `McpInvocationContext` for session scoping.

### 6. Capability
- **Edit** `apps/server/src/mcp/McpSessionRegistry.ts` (and the capability type)
— add `"students"`. Enable it for the thread when `/student` is used.

### 7. Client — broker request handler
- **New** `apps/web/src/students/studentToolBridge.ts` (or alongside the route)
— subscribe to `StudentsBroker` requests over WS; for each op:
`getStudents()` → apply (`create`/`update`/`delete` rebuild the list,
`findStudents`/`get` are read-only) → `normalizeAndValidateStudentInput` →
`setStudents(list)` → return result (created/updated student or validation
errors). Generate `id` via `crypto.randomUUID()` on create; set
`createdAt`/`updatedAt`. On validation failure, return errors so the agent
corrects or asks.
- **Live refresh:** publish a lightweight "students changed" signal so a mounted
`/students` route reloads (small store or event the route subscribes to).

### 8. `/student` slash command
- **Edit** `apps/web/src/components/chat/ChatComposer.tsx` — register `/student`
in the slash menu; selecting it (a) inserts a short primer, and (b) flags the
turn to enable the `"students"` capability. Hide/disable the entry when the
selected provider is **OpenCode** (no MCP).
- Define the primer text (schema summary + "use the student tools; confirm
before updating/deleting; ask to disambiguate duplicate names").

## Testing

- **Shared logic:** unit-test `normalizeAndValidateStudentInput` (name required,
postal-6-digit-when-partial, empty-parent drop, subject split, phone country).
- **Phone fix:** `PhoneField` stores `"SG"`; `whatsAppLink`/`telegramLink` now
produce `wa.me/65…` / `t.me/+65…`; legacy `"+65"` tolerated on read.
- **Toolkit handlers:** unit-test each op against a fake broker — happy path,
validation-error passthrough, `update`/`delete` approval accept + reject.
- **Broker:** request/response round-trip + session scoping (mirror preview
broker tests).
- **Client bridge:** create/update/delete/list/find against an in-memory
`localApi`; assert `setStudents` receives the correct whole list.
- **Manual:** `/student add …` (auto-applies, appears in roster live);
`/student rename / change subjects …` (confirmation prompt → persists);
`/student delete …` (confirmation → removed); duplicate-name disambiguation;
OpenCode hides `/student`.

## Out of scope (deferred)

- Injecting a selected student's context into coding prompts; "Chat about this
student" button (still **Iteration C**).
- OpenCode support (no MCP) — `/student` hidden there.
- Bulk import from files/spreadsheets; parents/schools as first-class entities;
non-Singapore addresses; multi-user/auth/sync.
- Any new direct (non-CLI) LLM call path — not needed; the existing agent +
toolkit does the work.

## The C seam (design-for, don't build)

- The `StudentsBroker` + capability flag are the same mechanism C will use to let
the agent *read* student context into a coding/tutoring conversation.
- Standardized `SG/MY/CN` phone codes make the C-iteration WhatsApp/Telegram deep
links work with no further change.
- Keeping one persistence path (`localApi`) means C never has to reconcile a
second copy of the roster.

## Suggested build order (each independently shippable)

1. Extract shared validation module (§1) — pure refactor, no behavior change.
2. Phone country fix (§2) — **already done** ahead of this plan; verify only.
3. Tool contracts (§3).
4. Server toolkit + broker + capability (§4–6), read-only ops first
(`list`/`find`/`get`) to prove the round-trip.
5. Client bridge (§7) — wire reads, then `create`, then `update`/`delete` with
approval + live refresh.
6. `/student` slash command + primer + OpenCode gating (§8).
7. Tests throughout.
Loading
Loading