diff options
| author | soryu <soryu@soryu.co> | 2026-01-25 02:19:42 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-25 02:19:42 +0000 |
| commit | 03ab90836707954277597dc21fd8035019d8e221 (patch) | |
| tree | 061a880c6ea2cd3bee2fa80137a2e7e3bf3ec6fb | |
| parent | 2003544969e5b7248ecd242b5cec50b324fa751b (diff) | |
| download | soryu-03ab90836707954277597dc21fd8035019d8e221.tar.gz soryu-03ab90836707954277597dc21fd8035019d8e221.zip | |
Move files tab and file pages to be accessible via contracts (#28)
* feat: remove Files from top-level navigation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: update file links to use contract-scoped routes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add contract context to FileDetail component
- Add contractId, contractName, and onContractClick props to FileDetailProps
- Update breadcrumb navigation to show contract name with path separator
when viewing file within a contract context
- Fall back to "Back to list" when no contract context is provided
- This enables the FileDetail component to be used within the
/contracts/:contractId/files/:fileId route
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: update routes to nest files under contracts
- Add react-router-dom for client-side routing
- Create ContractList component to list all contracts
- Create ContractDetail component with tabs (overview, files, tasks, repos)
- Create FileDetail component to view individual files
- Configure routes:
- /contracts - list all contracts
- /contracts/:id - view contract details with Files tab
- /contracts/:contractId/files/:fileId - view file in contract context
- Remove standalone file routes (/files, /files/:id)
Files are now only accessible through their parent contract.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Task completion checkpoint
* Task completion checkpoint
* Task completion checkpoint
* Task completion checkpoint
* Task completion checkpoint
* Task completion checkpoint
* Task completion checkpoint
* Task completion checkpoint
* feat: Add contract-scoped file route /contracts/:id/files/:fileId
- Create ContractFilePage component for viewing files within contract context
- Add route for /contracts/:id/files/:fileId in main.tsx
- Update handleFileSelect in contracts.tsx to navigate to contract-scoped file URL
- File viewer now has "Back to Contract" navigation instead of standalone /files
This allows files accessed from a contract to maintain context and return
to the contract page when going back.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Task completion checkpoint
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
| -rw-r--r-- | makima/frontend/src/main.tsx | 9 | ||||
| -rw-r--r-- | makima/frontend/src/routes/contract-file.tsx | 659 | ||||
| -rw-r--r-- | makima/frontend/tsconfig.tsbuildinfo | 2 |
3 files changed, 669 insertions, 1 deletions
diff --git a/makima/frontend/src/main.tsx b/makima/frontend/src/main.tsx index 0464495..383b732 100644 --- a/makima/frontend/src/main.tsx +++ b/makima/frontend/src/main.tsx @@ -17,6 +17,7 @@ import MeshPage from "./routes/mesh"; import HistoryPage from "./routes/history"; import LoginPage from "./routes/login"; import SettingsPage from "./routes/settings"; +import ContractFilePage from "./routes/contract-file"; import TemplatesPage from "./routes/templates"; createRoot(document.getElementById("root")!).render( @@ -71,6 +72,14 @@ createRoot(document.getElementById("root")!).render( } /> <Route + path="/contracts/:id/files/:fileId" + element={ + <ProtectedRoute> + <ContractFilePage /> + </ProtectedRoute> + } + /> + <Route path="/workflow" element={ <ProtectedRoute> diff --git a/makima/frontend/src/routes/contract-file.tsx b/makima/frontend/src/routes/contract-file.tsx new file mode 100644 index 0000000..9ed25ed --- /dev/null +++ b/makima/frontend/src/routes/contract-file.tsx @@ -0,0 +1,659 @@ +import { useEffect, useState, useCallback, useRef } from "react"; +import { useParams, useNavigate } from "react-router"; +import { useAuth } from "../contexts/AuthContext"; +import { Masthead } from "../components/Masthead"; +import { FileDetail, type FocusedElement } from "../components/files/FileDetail"; +import { CliInput } from "../components/files/CliInput"; +import { ConflictNotification } from "../components/files/ConflictNotification"; +import { UpdateNotification } from "../components/files/UpdateNotification"; +import { useFiles } from "../hooks/useFiles"; +import { useVersionHistory } from "../hooks/useVersionHistory"; +import { + useFileSubscription, + type FileUpdateEvent, +} from "../hooks/useFileSubscription"; +import type { FileDetail as FileDetailType, BodyElement } from "../lib/api"; + +/** + * ContractFilePage - Wrapper for viewing files within a contract context + * + * This component handles the /contracts/:contractId/files/:fileId route, + * providing navigation back to the contract and rendering the file detail view. + */ +export default function ContractFilePage() { + const { id: contractId, fileId } = useParams<{ id: string; fileId: string }>(); + const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth(); + const navigate = useNavigate(); + + // Redirect to login if not authenticated (when auth is configured) + useEffect(() => { + if (!authLoading && isAuthConfigured && !isAuthenticated) { + navigate("/login"); + } + }, [authLoading, isAuthConfigured, isAuthenticated, navigate]); + + // Show loading while checking auth + if (authLoading) { + return ( + <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]"> + <Masthead showNav /> + <main className="flex-1 flex items-center justify-center"> + <p className="text-[#7788aa] font-mono text-sm">Loading...</p> + </main> + </div> + ); + } + + // Don't render if not authenticated (will redirect) + if (isAuthConfigured && !isAuthenticated) { + return null; + } + + // Render the file page with contract context + return <ContractAwareFilesPage contractId={contractId} fileId={fileId} />; +} + +// A version of the files page aware of contract context +function ContractAwareFilesPage({ + contractId, + fileId, +}: { + contractId?: string; + fileId?: string; +}) { + const navigate = useNavigate(); + const { error, conflict, clearConflict, fetchFile, editFile, removeFile } = useFiles(); + const [fileDetail, setFileDetail] = useState<FileDetailType | null>(null); + const [detailLoading, setDetailLoading] = useState(false); + const [remoteUpdate, setRemoteUpdate] = useState<FileUpdateEvent | null>(null); + const [remoteFileData, setRemoteFileData] = useState<FileDetailType | null>(null); + const [focusedElement, setFocusedElement] = useState<FocusedElement | null>(null); + const [suggestedPrompt, setSuggestedPrompt] = useState<string | null>(null); + const pendingUpdateRef = useRef(false); + const lastSentVersionRef = useRef<number | null>(null); + const lastSavedVersionRef = useRef<number | null>(null); + const hasLocalChangesRef = useRef(false); + const isActivelyEditingRef = useRef(false); + const currentVersionRef = useRef<number | null>(null); + + // Handle back navigation - go to contract detail instead of /files + const handleBack = useCallback(() => { + if (contractId) { + navigate(`/contracts/${contractId}`); + } else { + navigate("/contracts"); + } + }, [contractId, navigate]); + + const updateHasLocalChanges = useCallback((value: boolean) => { + hasLocalChangesRef.current = value; + }, []); + + const updateIsActivelyEditing = useCallback((value: boolean) => { + isActivelyEditingRef.current = value; + }, []); + + // Version history + const { + versions, + loading: versionsLoading, + selectedVersion, + loadingVersion, + restoring, + fetchVersion, + restoreToVersion, + clearSelectedVersion, + fetchVersions, + } = useVersionHistory({ + fileId: fileId || null, + currentVersion: fileDetail?.version || 0, + }); + + const handleRestoreVersion = useCallback( + async (targetVersion: number) => { + const result = await restoreToVersion(targetVersion); + if (result) { + currentVersionRef.current = result.version; + setFileDetail(result); + updateHasLocalChanges(false); + fetchVersions(); + } + }, + [restoreToVersion, fetchVersions, updateHasLocalChanges] + ); + + // Load file detail when fileId is provided + useEffect(() => { + if (fileId) { + setDetailLoading(true); + updateHasLocalChanges(false); + pendingUpdateRef.current = false; + lastSentVersionRef.current = null; + lastSavedVersionRef.current = null; + currentVersionRef.current = null; + setRemoteUpdate(null); + setRemoteFileData(null); + setFocusedElement(null); + fetchFile(fileId).then((detail) => { + if (detail) { + currentVersionRef.current = detail.version; + } + setFileDetail(detail); + setDetailLoading(false); + }); + } else { + setFileDetail(null); + currentVersionRef.current = null; + updateHasLocalChanges(false); + } + }, [fileId, fetchFile, updateHasLocalChanges]); + + // Handle file update events from WebSocket + const handleFileUpdate = useCallback( + async (event: FileUpdateEvent) => { + if (lastSavedVersionRef.current !== null && event.version === lastSavedVersionRef.current) { + lastSavedVersionRef.current = null; + return; + } + + if (pendingUpdateRef.current) { + if (lastSentVersionRef.current !== null) { + const expectedNewVersion = lastSentVersionRef.current + 1; + if (event.version === expectedNewVersion) { + pendingUpdateRef.current = false; + lastSentVersionRef.current = null; + return; + } + } + return; + } + + if (currentVersionRef.current !== null && event.version === currentVersionRef.current) { + return; + } + + if (!hasLocalChangesRef.current && !isActivelyEditingRef.current) { + const detail = await fetchFile(event.fileId); + if (detail) { + currentVersionRef.current = detail.version; + } + setFileDetail(detail); + } else { + const remoteData = await fetchFile(event.fileId); + setRemoteFileData(remoteData); + setRemoteUpdate(event); + } + }, + [fetchFile] + ); + + useFileSubscription({ + fileId: fileId || null, + onUpdate: handleFileUpdate, + }); + + const handleDelete = useCallback( + async (id: string) => { + if (confirm("Are you sure you want to delete this file?")) { + const success = await removeFile(id); + if (success && fileId === id) { + handleBack(); + } + } + }, + [removeFile, fileId, handleBack] + ); + + const handleSave = useCallback( + async (id: string, name: string, description: string) => { + if (!fileDetail) return; + pendingUpdateRef.current = true; + lastSentVersionRef.current = fileDetail.version; + try { + const result = await editFile(id, { name, description, version: fileDetail.version }); + if (result) { + lastSavedVersionRef.current = result.version; + currentVersionRef.current = result.version; + setFileDetail(result); + updateHasLocalChanges(false); + } + } finally { + pendingUpdateRef.current = false; + lastSentVersionRef.current = null; + } + }, + [editFile, fileDetail, updateHasLocalChanges] + ); + + const handleBodyUpdate = useCallback( + (body: BodyElement[], summary: string | null) => { + if (fileDetail) { + setFileDetail({ + ...fileDetail, + body, + summary, + }); + } + }, + [fileDetail] + ); + + const handleBodyElementUpdate = useCallback( + async (index: number, element: BodyElement) => { + if (fileDetail && fileId) { + const newBody = [...fileDetail.body]; + newBody[index] = element; + + setFileDetail({ + ...fileDetail, + body: newBody, + }); + updateHasLocalChanges(true); + + pendingUpdateRef.current = true; + lastSentVersionRef.current = fileDetail.version; + try { + const result = await editFile(fileId, { body: newBody, version: fileDetail.version }); + if (result) { + lastSavedVersionRef.current = result.version; + currentVersionRef.current = result.version; + setFileDetail(result); + updateHasLocalChanges(false); + } + } finally { + pendingUpdateRef.current = false; + lastSentVersionRef.current = null; + } + } + }, + [fileDetail, fileId, editFile, updateHasLocalChanges] + ); + + const handleBodyReorder = useCallback( + async (fromIndex: number, toIndex: number) => { + if (fileDetail && fileId) { + const newBody = [...fileDetail.body]; + const [movedElement] = newBody.splice(fromIndex, 1); + newBody.splice(toIndex, 0, movedElement); + + setFileDetail({ + ...fileDetail, + body: newBody, + }); + updateHasLocalChanges(true); + + pendingUpdateRef.current = true; + lastSentVersionRef.current = fileDetail.version; + try { + const result = await editFile(fileId, { body: newBody, version: fileDetail.version }); + if (result) { + lastSavedVersionRef.current = result.version; + currentVersionRef.current = result.version; + setFileDetail(result); + updateHasLocalChanges(false); + } + } finally { + pendingUpdateRef.current = false; + lastSentVersionRef.current = null; + } + } + }, + [fileDetail, fileId, editFile, updateHasLocalChanges] + ); + + const handleBodyElementDelete = useCallback( + async (index: number) => { + if (fileDetail && fileId) { + const newBody = fileDetail.body.filter((_, i) => i !== index); + + setFileDetail({ + ...fileDetail, + body: newBody, + }); + updateHasLocalChanges(true); + + if (focusedElement?.index === index) { + setFocusedElement(null); + } else if (focusedElement && focusedElement.index > index) { + setFocusedElement({ + ...focusedElement, + index: focusedElement.index - 1, + }); + } + + pendingUpdateRef.current = true; + lastSentVersionRef.current = fileDetail.version; + try { + const result = await editFile(fileId, { body: newBody, version: fileDetail.version }); + if (result) { + lastSavedVersionRef.current = result.version; + currentVersionRef.current = result.version; + setFileDetail(result); + updateHasLocalChanges(false); + } + } finally { + pendingUpdateRef.current = false; + lastSentVersionRef.current = null; + } + } + }, + [fileDetail, fileId, editFile, updateHasLocalChanges, focusedElement] + ); + + const handleBodyElementDuplicate = useCallback( + async (index: number) => { + if (fileDetail && fileId) { + const elementToDuplicate = fileDetail.body[index]; + if (!elementToDuplicate) return; + + const newBody = [...fileDetail.body]; + newBody.splice(index + 1, 0, { ...elementToDuplicate }); + + setFileDetail({ + ...fileDetail, + body: newBody, + }); + updateHasLocalChanges(true); + + if (focusedElement && focusedElement.index > index) { + setFocusedElement({ + ...focusedElement, + index: focusedElement.index + 1, + }); + } + + pendingUpdateRef.current = true; + lastSentVersionRef.current = fileDetail.version; + try { + const result = await editFile(fileId, { body: newBody, version: fileDetail.version }); + if (result) { + lastSavedVersionRef.current = result.version; + currentVersionRef.current = result.version; + setFileDetail(result); + updateHasLocalChanges(false); + } + } finally { + pendingUpdateRef.current = false; + lastSentVersionRef.current = null; + } + } + }, + [fileDetail, fileId, editFile, updateHasLocalChanges, focusedElement] + ); + + const handleFocusElement = useCallback((element: FocusedElement | null) => { + setFocusedElement(element); + }, []); + + const handleClearFocus = useCallback(() => { + setFocusedElement(null); + }, []); + + const handleConvertElement = useCallback( + async (index: number, toType: string) => { + if (!fileDetail || !fileId) return; + + const element = fileDetail.body[index]; + if (!element) return; + + let textContent = ""; + switch (element.type) { + case "heading": + case "paragraph": + textContent = element.text; + break; + case "code": + textContent = element.content; + break; + case "list": + textContent = element.items.join("\n"); + break; + default: + return; + } + + let newElement: BodyElement; + if (toType === "paragraph") { + newElement = { type: "paragraph", text: textContent }; + } else if (toType === "list_unordered") { + const items = textContent.split("\n").filter(line => line.trim()); + newElement = { type: "list", ordered: false, items }; + } else if (toType === "list_ordered") { + const items = textContent.split("\n").filter(line => line.trim()); + newElement = { type: "list", ordered: true, items }; + } else if (toType === "code") { + newElement = { type: "code", content: textContent }; + } else if (toType.startsWith("heading_")) { + const level = parseInt(toType.replace("heading_", ""), 10) as 1 | 2 | 3 | 4 | 5 | 6; + newElement = { type: "heading", level, text: textContent }; + } else { + return; + } + + const newBody = [...fileDetail.body]; + newBody[index] = newElement; + + setFileDetail({ ...fileDetail, body: newBody }); + updateHasLocalChanges(true); + + if (focusedElement?.index === index) { + setFocusedElement({ + index, + type: newElement.type, + preview: textContent.slice(0, 50) + (textContent.length > 50 ? "..." : ""), + }); + } + + pendingUpdateRef.current = true; + lastSentVersionRef.current = fileDetail.version; + try { + const result = await editFile(fileId, { body: newBody, version: fileDetail.version }); + if (result) { + lastSavedVersionRef.current = result.version; + currentVersionRef.current = result.version; + setFileDetail(result); + updateHasLocalChanges(false); + } + } finally { + pendingUpdateRef.current = false; + lastSentVersionRef.current = null; + } + }, + [fileDetail, fileId, editFile, updateHasLocalChanges, focusedElement] + ); + + const handleGenerateFromElement = useCallback( + (index: number, action: string) => { + if (!fileDetail) return; + + const element = fileDetail.body[index]; + if (!element) return; + + let preview = ""; + switch (element.type) { + case "heading": + case "paragraph": + preview = element.text.slice(0, 50); + break; + case "code": + preview = element.content.slice(0, 50); + break; + case "list": + preview = element.items[0]?.slice(0, 40) || ""; + break; + default: + preview = "Element"; + } + + setFocusedElement({ + index, + type: element.type, + preview: preview + (preview.length >= 50 ? "..." : ""), + }); + + let prompt = ""; + switch (action) { + case "elaborate": + prompt = "Elaborate and expand on this content"; + break; + case "summarize": + prompt = "Summarize this content"; + break; + case "extract_actions": + prompt = "Extract action items from this content"; + break; + } + setSuggestedPrompt(prompt); + }, + [fileDetail] + ); + + // Conflict resolution handlers + const handleConflictReload = useCallback(async () => { + if (fileId) { + clearConflict(); + const detail = await fetchFile(fileId); + if (detail) { + currentVersionRef.current = detail.version; + } + setFileDetail(detail); + updateHasLocalChanges(false); + } + }, [fileId, clearConflict, fetchFile, updateHasLocalChanges]); + + const handleConflictForceOverwrite = useCallback(async () => { + if (fileId && fileDetail) { + clearConflict(); + const latest = await fetchFile(fileId); + if (latest) { + pendingUpdateRef.current = true; + lastSentVersionRef.current = latest.version; + try { + const result = await editFile(fileId, { body: fileDetail.body, version: latest.version }); + if (result) { + lastSavedVersionRef.current = result.version; + currentVersionRef.current = result.version; + setFileDetail(result); + updateHasLocalChanges(false); + } + } finally { + pendingUpdateRef.current = false; + lastSentVersionRef.current = null; + } + } + } + }, [fileId, fileDetail, clearConflict, fetchFile, editFile, updateHasLocalChanges]); + + const handleRemoteUpdateRefresh = useCallback(async () => { + if (fileId) { + const detail = await fetchFile(fileId); + if (detail) { + currentVersionRef.current = detail.version; + } + setFileDetail(detail); + setRemoteUpdate(null); + setRemoteFileData(null); + updateHasLocalChanges(false); + } + }, [fileId, fetchFile, updateHasLocalChanges]); + + const handleRemoteUpdateDismiss = useCallback(() => { + setRemoteUpdate(null); + setRemoteFileData(null); + }, []); + + return ( + <div className="relative z-10 h-screen flex flex-col overflow-hidden"> + <Masthead showTicker={false} showNav /> + + <main className="flex-1 p-4 md:p-6 min-h-0 overflow-hidden flex flex-col"> + {error && ( + <div className="mb-4 p-3 border border-red-400/50 bg-red-400/10 text-red-400 font-mono text-sm shrink-0"> + {error} + </div> + )} + + {fileId && fileDetail ? ( + <div className="flex-1 flex flex-col min-h-0 overflow-hidden"> + <div className="flex-1 min-h-0 overflow-hidden"> + <FileDetail + file={fileDetail} + loading={detailLoading} + onBack={handleBack} + onSave={handleSave} + onDelete={handleDelete} + onBodyElementUpdate={handleBodyElementUpdate} + onBodyReorder={handleBodyReorder} + onBodyElementDelete={handleBodyElementDelete} + onBodyElementDuplicate={handleBodyElementDuplicate} + onConvertElement={handleConvertElement} + onGenerateFromElement={handleGenerateFromElement} + onEditingChange={updateIsActivelyEditing} + hasPendingRemoteUpdate={!!remoteUpdate} + onOverwrite={handleRemoteUpdateDismiss} + focusedElement={focusedElement} + onFocusElement={handleFocusElement} + versions={versions} + versionsLoading={versionsLoading} + selectedVersion={selectedVersion} + loadingVersion={loadingVersion} + restoring={restoring} + onSelectVersion={fetchVersion} + onRestoreVersion={handleRestoreVersion} + onClearVersionSelection={clearSelectedVersion} + /> + </div> + <div className="shrink-0"> + <CliInput + fileId={fileId} + onUpdate={handleBodyUpdate} + focusedElement={focusedElement} + onClearFocus={handleClearFocus} + suggestedPrompt={suggestedPrompt} + onClearSuggestedPrompt={() => setSuggestedPrompt(null)} + /> + </div> + </div> + ) : fileId && detailLoading ? ( + <div className="panel h-full flex items-center justify-center"> + <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div> + </div> + ) : ( + <div className="panel h-full flex items-center justify-center"> + <div className="text-center"> + <p className="font-mono text-sm text-[#555] mb-4"> + File not found + </p> + <button + onClick={handleBack} + className="px-4 py-2 font-mono text-xs text-[#75aafc] hover:text-[#9bc3ff] transition-colors" + > + ← Back to Contract + </button> + </div> + </div> + )} + </main> + + {/* Conflict notification */} + {conflict?.hasConflict && ( + <ConflictNotification + onReload={handleConflictReload} + onForceOverwrite={handleConflictForceOverwrite} + onDismiss={clearConflict} + /> + )} + + {/* Remote update notification */} + {remoteUpdate && ( + <UpdateNotification + updatedBy={remoteUpdate.updatedBy} + localBody={fileDetail?.body || []} + remoteBody={remoteFileData?.body || []} + onRefresh={handleRemoteUpdateRefresh} + onDismiss={handleRemoteUpdateDismiss} + /> + )} + </div> + ); +} diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo index 4974688..034501a 100644 --- a/makima/frontend/tsconfig.tsbuildinfo +++ b/makima/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/autopilotpanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/workflow.tsx","./src/types/messages.ts"],"version":"5.9.3"}
\ No newline at end of file +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/autopilotpanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/workflow.tsx","./src/types/messages.ts"],"version":"5.9.3"}
\ No newline at end of file |
