From 03ab90836707954277597dc21fd8035019d8e221 Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 25 Jan 2026 02:19:42 +0000 Subject: 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 * feat: update file links to use contract-scoped routes Co-Authored-By: Claude Opus 4.5 * 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 * 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 * 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 * Task completion checkpoint --------- Co-authored-by: Claude Opus 4.5 --- makima/frontend/src/main.tsx | 9 + makima/frontend/src/routes/contract-file.tsx | 659 +++++++++++++++++++++++++++ makima/frontend/tsconfig.tsbuildinfo | 2 +- 3 files changed, 669 insertions(+), 1 deletion(-) create mode 100644 makima/frontend/src/routes/contract-file.tsx (limited to 'makima') 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( @@ -70,6 +71,14 @@ createRoot(document.getElementById("root")!).render( } /> + + + + } + /> (); + 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 ( +
+ +
+

Loading...

+
+
+ ); + } + + // Don't render if not authenticated (will redirect) + if (isAuthConfigured && !isAuthenticated) { + return null; + } + + // Render the file page with contract context + return ; +} + +// 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(null); + const [detailLoading, setDetailLoading] = useState(false); + const [remoteUpdate, setRemoteUpdate] = useState(null); + const [remoteFileData, setRemoteFileData] = useState(null); + const [focusedElement, setFocusedElement] = useState(null); + const [suggestedPrompt, setSuggestedPrompt] = useState(null); + const pendingUpdateRef = useRef(false); + const lastSentVersionRef = useRef(null); + const lastSavedVersionRef = useRef(null); + const hasLocalChangesRef = useRef(false); + const isActivelyEditingRef = useRef(false); + const currentVersionRef = useRef(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 ( +
+ + +
+ {error && ( +
+ {error} +
+ )} + + {fileId && fileDetail ? ( +
+
+ +
+
+ setSuggestedPrompt(null)} + /> +
+
+ ) : fileId && detailLoading ? ( +
+
Loading...
+
+ ) : ( +
+
+

+ File not found +

+ +
+
+ )} +
+ + {/* Conflict notification */} + {conflict?.hasConflict && ( + + )} + + {/* Remote update notification */} + {remoteUpdate && ( + + )} +
+ ); +} 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 -- cgit v1.2.3