Dev#3566
Conversation
…with all student domain schemas - Created StudentId branded entity ID - Created PhoneNumber schema (country + number) - Created SingaporeAddress schema with optional fields - Created Parent schema with optional fields - Created Student schema with all required and optional fields - Created StudentRegistryDocument schema (version + students array) - Exported all schemas and inferred types - Added export to index.ts Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added studentRegistryPath: string to DesktopEnvironmentShape interface and set its value to path.join(stateDir, 'students.json') in makeDesktopEnvironment, following the same pattern as savedEnvironmentRegistryPath. Note: Desktop typecheck currently fails due to phase dependencies (Phase 1 extended DesktopBridge interface, Phase 3 will implement the methods). This subtask's changes are complete and correct. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
… persistence - Created apps/desktop/src/settings/DesktopStudents.ts following DesktopSavedEnvironments pattern - Defined DesktopStudentsWriteError for error handling - Defined DesktopStudentsShape with getRegistry and setRegistry methods - Implemented StudentRegistryDocumentSchema using fromLenientJson - Implemented readRegistryDocument with graceful fallback to empty array on corrupt/missing file - Implemented atomic writeRegistryDocument with temp+rename for safe writes - Exported production layer using FileSystem + DesktopEnvironment.studentRegistryPath - Exported layerTest for in-memory testing - Stripped all encryption/SafeStorage code (students have no secrets) - Added DesktopStudents.layer to desktopFoundationLayer in main.ts Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…egistry - Added GET_STUDENTS_CHANNEL and SET_STUDENTS_CHANNEL to channels.ts - Created students.ts with getStudents and setStudents IPC methods - Added getStudents and setStudents to preload bridge implementation - Methods follow makeIpcMethod pattern with proper Effect.fn naming - Handlers yield* DesktopStudents service and call getRegistry/setRegistry Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…top/src/ Imported getStudents and setStudents from ./methods/students.ts and registered them as IPC handlers in DesktopIpcHandlers.ts. The preload.ts file already had the necessary desktopBridge methods exposed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…sistence section - Add STUDENT_REGISTRY_STORAGE_KEY constant - Import Student schema from contracts - Add BrowserStudentRegistryDocumentSchema with optional fields - Implement readBrowserStudents() returning readonly Student[] - Implement writeBrowserStudents() for localStorage persistence - Add getStudents/setStudents to LocalApi persistence section - Follow existing pattern: check window.desktopBridge first, fall back to browser storage - Update all test mocks to include new persistence methods Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Created apps/web/src/routes/students.tsx with: - Auth gate using beforeLoad (checks authGateState.status) - Two-pane split layout (left: student list, right: detail/form) - SidebarInset for consistent chrome with header - Student roster loading via localApi.persistence.getStudents() - 'New Student' button in header - Empty state for when no students exist - Basic student list rendering (alphabetical) - Welcome message in right pane when no student selected The route tree was auto-generated by TanStack Router plugin. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…udentList.tsx Implemented StudentList component with: - Alphabetically sorted student list by name - Clickable rows with active student highlighting - Empty state with 'No students yet' message and 'Add your first student' button - Props: students array, selectedStudentId, onSelectStudent, onNewStudent Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
… apps/we Created three field sub-components for the student form: 1. PhoneField.tsx - Country dropdown (SG, MY, CN) with phone number input. Default to Singapore (+65). Accepts value/onChange props for controlled input. 2. ParentRows.tsx - Dynamic add/remove parent rows. Each row has name, relationship, and optional phone (PhoneField). Add/remove buttons provided. Accepts parents array + onChange callback. 3. AddressFields.tsx - Singapore address fields (block, street, building, unit, postalCode). All fields optional, but if any field has value, postalCode must be 6 digits. Shows inline validation error. All components follow existing code patterns from ProviderSettingsForm.tsx and use shadcn/ui components (Input, Select, Button, Label). TypeScript compiles without errors with exactOptionalPropertyTypes enabled. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…udentForm.tsx Implemented StudentForm component with: - Create/edit mode support - Name field (required, validated for non-empty trimmed value) - Phone field using PhoneField component - Subjects field (comma-separated input) - School field (optional text) - Address fields using AddressFields component with postal code validation - Parent rows using ParentRows component (empty rows auto-dropped on save) - Notes field (textarea) - Proper UUID generation via StudentId.make(randomUUID()) - Form validation for name and postal code - Tailwind CSS styling consistent with existing forms Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…nks.ts with deep-link helpers Implemented three deep-link helper functions: - whatsAppLink: builds WhatsApp URL from PhoneNumber - telegramLink: builds Telegram URL from PhoneNumber - googleMapsLink: builds Google Maps search URL from SingaporeAddress All functions handle edge cases by returning empty string for invalid inputs. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented StudentDetail.tsx with: - Read-only view of all student fields in structured layout - Edit and Delete action buttons with confirmation dialog - Deep-link buttons for WhatsApp/Telegram (student + parent phones) - Google Maps button for address - All links open via localApi.shell.openExternal() - Proper field display with icons and formatting - Metadata section showing created/updated timestamps Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…t.ts with @effect/vitest tests for Student schema - StudentId branded type tests (enforce non-empty, trim whitespace) - PhoneNumber schema tests (all fields, trimming, validation) - SingaporeAddress schema tests (all/partial fields, empty object) - Parent schema tests (with/without phone, optional fields) - Student schema tests (all fields, required only, validation) - StudentRegistryDocument tests (populated/empty arrays, version validation) All tests passing using Schema.decodeUnknownEffect pattern.
…udents.test.ts - Created comprehensive test suite following DesktopSavedEnvironments.test.ts pattern - Implemented makeLayer helper with temp directory + DesktopEnvironment.layer + NodeServices.layer - Implemented withStudents helper wrapping Effect.gen with temp directory scope - Tests cover: persist/reload round-trip, missing file, empty JSON, malformed JSON, atomic write - All tests pass Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
… (qa-requested)
Fixes WhatsApp/Telegram deep link generation and phone display issues.
Changes:
- PhoneField.tsx: Store country codes ("SG"/"MY"/"CN") instead of dial codes ("+65"/"+60"/"+86")
- students.ts: Tighten PhoneNumber.country schema to Schema.Union of literals ("SG", "MY", "CN")
- StudentDetail.tsx: Fix phone display mapping (was showing "++65", now shows "+65")
- StudentForm.tsx: Update type annotations to use CountryCode type
- DesktopStudents.ts: Update PhoneNumberSchema to use CountryCode
- DesktopStudents.test.ts: Update test data to use "SG" instead of "+65"
- students.test.ts: Update whitespace trimming test (literals don't trim)
Verified:
- All tests pass (contracts: 185, desktop: 164, web: 1154)
- Full typecheck passes across all packages
- WhatsApp/Telegram links now generate correctly
- Phone display shows correct dial code
QA Fix Session: 1
… helper - Created packages/contracts/src/students.ts with: - StudentId branded type following baseSchemas pattern - StudentSchema with id, name, subjects, school, and optional workspaceFolder - deriveStudentSlug() helper function for URL-safe slug generation - Updated packages/contracts/src/index.ts to export students module - Follows patterns from settings.ts for schema definitions - Verification passes with no type errors Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ktopBrid Added renderMarkdownToPdf, openPath, and ensureStudentWorkspace methods to DesktopBridge interface with corresponding Schema definitions for input/output types. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…t3Home to tutoratlasHome Changes: - Updated config.ts: Renamed T3CODE_HOME env var to TUTORATLAS_HOME - Updated config.ts: Renamed t3Home field to tutoratlasHome - Updated os-jank.ts: Changed default directory from .t3 to .tutoratlas Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…t3Home to tutoratlasHome Renamed environment variable T3CODE_HOME to TUTORATLAS_HOME and field t3Home to tutoratlasHome across dev-runner, electron-launcher, and desktop backend configuration files. Updated all references including default home directory path, env variable assignments, flag definitions, and schema fields. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…opEnvironment, DesktopCloudAuthTokenStore, DesktopObservability, DesktopUpdates Replace T3CODE_HOME with TUTORATLAS_HOME in env setup and DesktopConfig.layerTest calls. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…opSavedE Replace T3CODE_HOME with TUTORATLAS_HOME in DesktopConfig.layerTest calls for DesktopSavedEnvironments.test.ts, DesktopAppSettings.test.ts, DesktopStudents.test.ts, and DesktopClientSettings.test.ts. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…er/script test files Renamed T3CODE_HOME → TUTORATLAS_HOME, '.t3' → '.tutoratlas', t3Home → tutoratlasHome in all test files: - apps/desktop/src/backend/DesktopServerExposure.test.ts - apps/desktop/src/backend/DesktopBackendConfiguration.test.ts - apps/desktop/src/backend/DesktopBackendManager.test.ts - apps/server/src/cli/config.test.ts - scripts/dev-runner.test.ts Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…pace changes - Update DesktopAppIdentity tests to expect 'TutorAtlas (Alpha)' instead of 'T3 Code (Alpha)' - Update DesktopEnvironment test to expect backendCwd at ~/tutoratlas (new workspace root) - Legacy path test correctly expects 'T3 Code (Alpha)' when legacy path exists
…eplace T3CODE_HOME with TUTORATLAS_HOME - Replaced 7 occurrences of $T3CODE_HOME/userdata/logs/ with $TUTORATLAS_HOME/userdata/logs/ in docs/operations/observability.md - Updated .plans/atlas/03-unified-workspace.md references at lines 28 and 145 - Added R2 VSync log documentation noting ERROR:ui/gl/gl_surface_presentation_helper.cc:260 is benign noise on Linux Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…d a sect Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…faces - Update EnsureStudentWorkspaceInputSchema to accept name (Schema.String) + id (StudentId) instead of just studentId - Update EnsureStudentWorkspaceResultSchema to include workspaceFolder field - Add DeleteStudentWorkspaceInputSchema (workspaceFolder: Schema.String) - Add DeleteStudentWorkspaceResultSchema (success + error) - Add deleteStudentWorkspace to DesktopBridge interface - Add deleteStudentWorkspace to LocalApi materials interface Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ant to channels.ts Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…eStudent - Add deleteStudentWorkspace method to DesktopWorkspaceShape interface - Implement path resolution via path.join(workspaceRoot, workspaceFolder) - Use fs.realPath to resolve symlinks before deletion - Validate resolved path is strictly inside <workspaceRoot>/students/ - Implement recursive directory removal - Update ensureStudentWorkspace to return workspaceFolder (relative path) - Update error class to support context field for better error messages - Implement in both main layer and test layer Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…and add deleteStudentWorkspace - Updated ensureStudentWorkspace to accept name+id input - Derive slug server-side via deriveStudentSlug(name) + sanitize + id suffix - Return both workspacePath (absolute) and workspaceFolder (relative) in result - Added deleteStudentWorkspace IPC handler with path validation - Both handlers follow the existing IPC method pattern with Effect.match Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
… in prel Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…orkspace, remove sort/group menus - Rename sidebar header from 'Projects' to 'Workspace' - Remove 'Sort projects' MenuGroup block from ProjectSortMenu - Remove 'Group projects' MenuGroup block from ProjectSortMenu - Remove SIDEBAR_SORT_LABELS constant (orphaned) - Remove PROJECT_GROUPING_MODE_LABELS constant (replaced with inline strings) - Remove projectGroupingModeDescription function (replaced with inline logic) - Remove handleProjectSortOrderChange handler (orphaned) - Remove handleProjectGroupingModeChange handler (orphaned) - Remove orphaned props from ProjectSortMenu component - Remove orphaned props from SidebarProjectsContent component - Remove SidebarProjectSortOrder type import (orphaned) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…Palette - Remove 'New thread in...' project-picker submenu - Remove projectSearchItems useMemo (lines 628-643) - Remove projectThreadItems useMemo (lines 645-679) - Remove 'Add project' action item block (lines 1011-1039) - Remove buildProjectActionItems function from CommandPalette.logic.ts - Clean up orphaned imports: buildProjectActionItems, ProjectFavicon Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…pace wrapper Added deleteStudentWorkspace method in materials section following the same pattern as ensureStudentWorkspace: - Gates on window.desktopBridge availability - Delegates to bridge.deleteStudentWorkspace(input) - Throws descriptive error in browser-only mode Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…dleGenerateMaterials - Removed 'Generate materials' button JSX (lines 119-127) - Removed handleGenerateMaterials handler function (lines 50-107) - Removed isGenerating state - Removed useHandleNewThread hook import and usage - Removed orphaned imports: useState, FolderIcon, toastManager Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…create b Integrated student workspace folder lifecycle into students.tsx route: - In handleSave (create): Call ensureStudentWorkspace IPC after initial persist, store workspaceFolder on student record - In handleDelete: Check for workspaceFolder, show folder-aware confirmation, call deleteStudentWorkspace IPC before roster removal - Handle pre-R4 students (no workspaceFolder) by skipping folder operations Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…s and R4 schema changes Changes: 1. apps/desktop/src/workspace/DesktopWorkspace.ts: - Fixed SystemError construction to use Effect.die for security violations - Removed 'reason' field (not supported by SystemError) 2. apps/web/src/localApi.test.ts: - Added workspaceFolder field to ensureStudentWorkspace mock - Added deleteStudentWorkspace mock method 3. apps/web/src/components/settings/SettingsPanels.browser.tsx: - Added workspaceFolder field to ensureStudentWorkspace mock - Added deleteStudentWorkspace mock method All typechecks pass (desktop, web, server, scripts, contracts). External dependency errors (@noble, jose, yaml) are unrelated to our changes. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…he-content-of-the-file-to-generate- Auto claude/009 i need to see the content of the file to generate
Replace the Deferred-based did-finish-load/did-fail-load wait with an Effect.callback whose finalizer removes both listeners when the load is interrupted (e.g. by PDF_TIMEOUT_MS), preventing a webContents listener leak on timed-out renders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ed-workspace-feature Auto claude/008 implement unified workspace feature
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 7 potential issues.
There are 12 total unresolved issues (including 5 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 337bb04. Configure here.
| // Silent fail - continue with roster removal | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Roster deleted after failed workspace
High Severity
When deleting a student, the application removes them from the roster and persists this change even if the deleteStudentWorkspace call fails. This can leave student workspace files on disk while the UI shows the student as deleted, leading to an inconsistent state.
Reviewed by Cursor Bugbot for commit 337bb04. Configure here.
| const confirmed = await localApi.dialogs.confirm( | ||
| `Delete student "${student.name}" and workspace folder "${student.workspaceFolder}"? This action cannot be undone.`, | ||
| ); | ||
| if (!confirmed) return; |
There was a problem hiding this comment.
Two delete confirmation dialogs
Medium Severity
Deleting a student with an associated workspace folder triggers two native confirmation dialogs. The first dialog omits mention of the workspace folder, while the second explicitly asks about deleting it, leading to redundant confirmation for users.
Reviewed by Cursor Bugbot for commit 337bb04. Configure here.
| const baseDir = Option.getOrElse(config.tutoratlasHome, () => path.join(homeDirectory, ".tutoratlas")); | ||
| const workspaceRoot = Option.getOrElse(config.tutoratlasWorkspace, () => | ||
| path.join(homeDirectory, "tutoratlas"), | ||
| ); |
There was a problem hiding this comment.
Legacy app data not migrated
High Severity
The app's base directory for user state now defaults to ~/.tutoratlas (or TUTORATLAS_HOME), no longer falling back to the previous ~/.t3 (or T3CODE_HOME). Upgraded installs don't migrate existing data, so rosters, settings, and tokens appear empty.
Reviewed by Cursor Bugbot for commit 337bb04. Configure here.
|
|
||
| const handleProjectButtonContextMenu = useCallback( | ||
| (event: React.MouseEvent<HTMLButtonElement>) => { | ||
| return; |
There was a problem hiding this comment.
Project context menu disabled wrongly
Low Severity
The handleProjectButtonContextMenu for project rows now immediately returns, preventing the custom context menu and its actions (like rename or delete) from appearing. This also allows the browser's default context menu to show.
Reviewed by Cursor Bugbot for commit 337bb04. Configure here.
| </div> | ||
|
|
||
| {isManualProjectSorting ? ( | ||
| {false ? ( |
There was a problem hiding this comment.
Manual project reorder always disabled
Medium Severity
The sidebar project list always renders the non-draggable branch because the condition was changed from isManualProjectSorting to literal false. Users who set manual project sort in settings can no longer drag-reorder workspace projects.
Reviewed by Cursor Bugbot for commit 337bb04. Configure here.
| } | ||
|
|
||
| // Recursively remove the directory | ||
| yield* input.fileSystem.remove(realPath, { recursive: true }); |
There was a problem hiding this comment.
Delete wipes all student folders
High Severity
The delete path guard treats a resolved path equal to the shared students directory as valid, then recursively removes it. A workspaceFolder value of students (allowed by the contract) can delete every student workspace under the tutor root, not one slug folder.
Reviewed by Cursor Bugbot for commit 337bb04. Configure here.
| ["VITE_DEV_SERVER_URL", process.env.VITE_DEV_SERVER_URL], | ||
| ["T3CODE_PORT", process.env.T3CODE_PORT], | ||
| ["T3CODE_HOME", process.env.T3CODE_HOME], | ||
| ["TUTORATLAS_HOME", process.env.TUTORATLAS_HOME], |
There was a problem hiding this comment.
🟡 Medium scripts/electron-launcher.mjs:107
The diff renames the exported env var on line 107 from T3CODE_HOME to TUTORATLAS_HOME, but LAUNCHER_VERSION was not bumped. buildMacLauncher() reuses an existing cached bundle whenever metadata.json matches, and expectedMetadata only includes launcherVersion, source bundle mtimes, icon mtime, bundle id, and protocol schemes — not the generated script contents or env var names. Developers who already have .electron-runtime/T3 Code (Dev).app will keep launching the old wrapper that still exports T3CODE_HOME, while the desktop app now reads TUTORATLAS_HOME. On those machines the app silently falls back to the default home directory instead of the configured one until the cached launcher is manually deleted. Bump LAUNCHER_VERSION so the cached launcher is invalidated and regenerated with the new env var name.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/scripts/electron-launcher.mjs around line 107:
The diff renames the exported env var on line 107 from `T3CODE_HOME` to `TUTORATLAS_HOME`, but `LAUNCHER_VERSION` was not bumped. `buildMacLauncher()` reuses an existing cached bundle whenever `metadata.json` matches, and `expectedMetadata` only includes `launcherVersion`, source bundle mtimes, icon mtime, bundle id, and protocol schemes — not the generated script contents or env var names. Developers who already have `.electron-runtime/T3 Code (Dev).app` will keep launching the old wrapper that still exports `T3CODE_HOME`, while the desktop app now reads `TUTORATLAS_HOME`. On those machines the app silently falls back to the default home directory instead of the configured one until the cached launcher is manually deleted. Bump `LAUNCHER_VERSION` so the cached launcher is invalidated and regenerated with the new env var name.
| return yield* Effect.die( | ||
| `Security: Path '${realPath}' is not strictly inside students directory '${realStudentsDir}'`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
🟡 Medium workspace/DesktopWorkspace.ts:84
The security check uses Effect.die(...) for an invalid path, but deleteStudentWorkspace is wrapped only with Effect.mapError(...). Defects are not caught by mapError, so an out-of-tree workspaceFolder such as ../other terminates the effect instead of surfacing as a DesktopWorkspaceError. Consider using Effect.fail(...) with a DesktopWorkspaceError so the invalid-path condition is handled as a normal error.
| return yield* Effect.die( | |
| `Security: Path '${realPath}' is not strictly inside students directory '${realStudentsDir}'`, | |
| ); | |
| } | |
| return yield* Effect.fail( | |
| new DesktopWorkspaceError({ | |
| cause: new PlatformError.PlatformError({ reason: "AccessDenied", error: new Error(`Security: Path '${realPath}' is not strictly inside students directory '${realStudentsDir}'`) }), | |
| context: "to delete student workspace", | |
| }), | |
| ); |
Also found in 1 other location(s)
apps/desktop/src/ipc/methods/workspace.ts:60
deleteStudentWorkspacewrapsworkspace.deleteStudentWorkspace(...)withEffect.match, but the implementation inDesktopWorkspaceusesEffect.die(...)whenworkspaceFolderresolves outside thestudentsdirectory.Effect.matchdoes not catch defects, so an invalid or maliciousworkspaceFoldermakes this handler reject the IPC call instead of returning the declared{ success: false, error }result shape.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/workspace/DesktopWorkspace.ts around lines 84-87:
The security check uses `Effect.die(...)` for an invalid path, but `deleteStudentWorkspace` is wrapped only with `Effect.mapError(...)`. Defects are not caught by `mapError`, so an out-of-tree `workspaceFolder` such as `../other` terminates the effect instead of surfacing as a `DesktopWorkspaceError`. Consider using `Effect.fail(...)` with a `DesktopWorkspaceError` so the invalid-path condition is handled as a normal error.
Also found in 1 other location(s):
- apps/desktop/src/ipc/methods/workspace.ts:60 -- `deleteStudentWorkspace` wraps `workspace.deleteStudentWorkspace(...)` with `Effect.match`, but the implementation in `DesktopWorkspace` uses `Effect.die(...)` when `workspaceFolder` resolves outside the `students` directory. `Effect.match` does not catch defects, so an invalid or malicious `workspaceFolder` makes this handler reject the IPC call instead of returning the declared `{ success: false, error }` result shape.
| : Option.getOrElse(config.xdgConfigHome, () => path.join(homeDirectory, ".config")); | ||
| const baseDir = Option.getOrElse(config.t3Home, () => path.join(homeDirectory, ".t3")); | ||
| const baseDir = Option.getOrElse(config.tutoratlasHome, () => path.join(homeDirectory, ".tutoratlas")); | ||
| const workspaceRoot = Option.getOrElse(config.tutoratlasWorkspace, () => |
There was a problem hiding this comment.
🟡 Medium app/DesktopEnvironment.ts:158
workspaceRoot is used directly from config.tutoratlasWorkspace without expanding ~ or resolving to an absolute path. When a user sets TUTORATLAS_WORKSPACE=~/tutoratlas, the value ~/tutoratlas is used as-is, so backendCwd is passed to the backend process as the literal relative path ~/tutoratlas, causing startup to fail with ENOENT. Consider expanding ~ to homeDirectory and resolving to an absolute path before using the value.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/app/DesktopEnvironment.ts around line 158:
`workspaceRoot` is used directly from `config.tutoratlasWorkspace` without expanding `~` or resolving to an absolute path. When a user sets `TUTORATLAS_WORKSPACE=~/tutoratlas`, the value `~/tutoratlas` is used as-is, so `backendCwd` is passed to the backend process as the literal relative path `~/tutoratlas`, causing startup to fail with `ENOENT`. Consider expanding `~` to `homeDirectory` and resolving to an absolute path before using the value.
| noBrowser: Schema.Boolean, | ||
| port: PortSchema, | ||
| t3Home: Schema.String, | ||
| tutoratlasHome: Schema.String, |
There was a problem hiding this comment.
🟡 Medium src/desktopBootstrap.ts:9
Renaming the required field t3Home to tutoratlasHome breaks wire compatibility. Effect Schema.Struct requires listed fields and disallows extra properties, so an older producer that sends { "t3Home": "..." } will be rejected as missing tutoratlasHome — a mixed-version desktop/server pair or stored bootstrap fixture will fail startup. Consider restoring the original field name or adding a compatibility layer (e.g., Schema.propertySignature with Schema.optional for both names, or decoding both and re-encoding under the new name) so older payloads still decode.
🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/contracts/src/desktopBootstrap.ts around line 9:
Renaming the required field `t3Home` to `tutoratlasHome` breaks wire compatibility. Effect `Schema.Struct` requires listed fields and disallows extra properties, so an older producer that sends `{ "t3Home": "..." }` will be rejected as missing `tutoratlasHome` — a mixed-version desktop/server pair or stored bootstrap fixture will fail startup. Consider restoring the original field name or adding a compatibility layer (e.g., `Schema.propertySignature` with `Schema.optional` for both names, or decoding both and re-encoding under the new name) so older payloads still decode.
|
|
||
| yield* waitForLoad.pipe( | ||
| Effect.timeout(PDF_TIMEOUT_MS), | ||
| Effect.catchTag("TimeoutError", () => |
There was a problem hiding this comment.
🟡 Medium pdf/DesktopPdfRenderer.ts:102
Effect.timeout fails with the tag "TimeoutException", but Effect.catchTag at line 102 matches "TimeoutError", so the catch never triggers. When the page load exceeds PDF_TIMEOUT_MS, the raw TimeoutException escapes instead of being converted to DesktopPdfRendererError, violating the advertised error type. Use Effect.catchTag("TimeoutException", …) so timeouts are mapped correctly.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/pdf/DesktopPdfRenderer.ts around line 102:
`Effect.timeout` fails with the tag `"TimeoutException"`, but `Effect.catchTag` at line 102 matches `"TimeoutError"`, so the catch never triggers. When the page load exceeds `PDF_TIMEOUT_MS`, the raw `TimeoutException` escapes instead of being converted to `DesktopPdfRendererError`, violating the advertised error type. Use `Effect.catchTag("TimeoutException", …)` so timeouts are mapped correctly.


What Changed
Why
UI Changes
Checklist
Note
High Risk
Large cross-cutting change (branding, data paths, workspace cwd, new persistence/IPC/PDF) can break existing installs, updates, and stored student data locations; PDF rendering and filesystem deletes are security- and data-loss-sensitive surfaces.
Overview
This PR pivots the fork toward TutorAtlas: user-facing branding and desktop
productNamemove from T3 Code to TutorAtlas, app data/env shifts fromT3CODE_HOME/.t3toTUTORATLAS_HOME/.tutoratlas, and the embedded serverbackendCwd/workspaceRootdefault to~/tutoratlaswithT3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWDenabled so launch can auto-open a single workspace project.Students land as a first-class feature:
@t3tools/contractsstudent schemas (optionalworkspaceFolder,deriveStudentSlug),DesktopStudentsatomic JSON registry + get/set IPC, preload bridge methods, and the web Students route/UI (form validation, phone country codes, deep links).DesktopWorkspacecreatesstudents/<slug>/under the workspace (optionalAGENTS.md), bootstraps.atlas/skills, and exposes ensure/delete/open-path IPC with path checks on delete.PDF materials:
DesktopPdfRendererrenders HTML via a hiddenBrowserWindow+printToPDFwith temp-then-rename writes;renderMarkdownToPdf/openPathIPC andElectronShell.openPathwire the web previewRenderPdfButtonpath described in the plans. SidebarisMaterialsThreadhides PR/git chrome for threads under/students/.Also adds extensive
.plans/Atlas roadmap docs,.gitignoreentries for Auto Claude, and an E2E verification write-up for the materials/PDF checklist—not runtime behavior by itself.Reviewed by Cursor Bugbot for commit 337bb04. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Rebrand app from T3 Code to TutorAtlas and add student roster and PDF rendering features
T3CODE_HOME→TUTORATLAS_HOME,t3Home→tutoratlasHome), environment paths (~/.t3→~/.tutoratlas), app IDs, and build artifact names.Studentschema in contracts, desktopDesktopStudentsservice for atomic on-disk persistence atenvironment.studentRegistryPath, IPC channels for get/set, browser localStorage fallback, and a/studentsroute with list/detail/form UI.DesktopWorkspace: creates<workspaceRoot>/students/<slug>directories, seedsAGENTS.md, bootstraps.atlas/skills/on startup, and supports secure deletion.markdownToHtmlembeds base64 woff2 fonts (DM Sans Variable, Source Serif 4 Variable) and print CSS into a full HTML document,DesktopPdfRendererrenders it via an offscreen ElectronBrowserWindowwith atomic file write, andRenderPdfButtonexposes this in the markdown preview UI.gen-brand-assetsscript that generates ICO, ICNS, and PNG icon assets fromassets/atlas/logo.svgusingsharp.backendCwdnow points toworkspaceRoot(~/tutoratlas) instead ofhomeDirectory/appRoot, which changes where the backend server is launched from.Macroscope summarized 337bb04.