@neuralis/brain-core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -35
- package/app/cards/DiffView.tsx +9 -7
- package/app/cards/README.md +1 -1
- package/app/cards/diffLine.tsx +2 -4
- package/app/filesystem/README.md +1 -1
- package/app/filesystem/api/index.ts +14 -5
- package/app/filesystem/domain/types.ts +24 -5
- package/app/filesystem/store/filesystemStore.ts +20 -1
- package/app/filesystem/store/listSlice.ts +2 -0
- package/app/filesystem/store/searchSlice.ts +24 -1
- package/app/filesystem/ui/FileTreeSearch.tsx +29 -2
- package/app/filesystem/ui/PendingChangesPanel.tsx +74 -17
- package/app/filesystem/ui/PendingReviewDialog.tsx +45 -20
- package/app/filesystem/ui/README.md +8 -7
- package/app/filesystem/ui/ReembedDialog.tsx +20 -9
- package/app/filesystem/ui/SourceDeleteConfirm.tsx +1 -1
- package/app/filesystem/ui/SourcesPanel.tsx +110 -17
- package/app/filesystem/ui/VectorHealthPanel.tsx +14 -0
- package/app/filesystem/ui/pendingChangesModel.ts +9 -5
- package/dist/app/chunks/{astro-LRAFGXNX.js → astro-B2J4AS2D.js} +1 -1
- package/dist/app/chunks/bash-KY6SAQTH.js +1 -0
- package/dist/app/chunks/c-OBOPYVZ7.js +1 -0
- package/dist/app/chunks/{chunk-5H7ZCOD3.js → chunk-2X3GNKJ3.js} +1 -1
- package/dist/app/chunks/{chunk-XIFGTNRM.js → chunk-JPJIVRFI.js} +1 -1
- package/dist/app/chunks/{chunk-RH3QY5LF.js → chunk-LEFIFWND.js} +1 -1
- package/dist/app/chunks/{chunk-G4RZ3UNX.js → chunk-N6RXX3PH.js} +1 -1
- package/dist/app/chunks/{chunk-QXXJ73P5.js → chunk-PZAW4HPB.js} +1 -1
- package/dist/app/chunks/{chunk-3QLE7Y4L.js → chunk-ZBYWWTO6.js} +1 -1
- package/dist/app/chunks/cpp-TVWVOTEQ.js +1 -0
- package/dist/app/chunks/css-MANZBYA4.js +1 -0
- package/dist/app/chunks/{elixir-M36XROA6.js → elixir-S725Z7EH.js} +1 -1
- package/dist/app/chunks/{elm-7BG6QAIK.js → elm-5WUUSTNB.js} +1 -1
- package/dist/app/chunks/graphql-UGOPR3WU.js +1 -0
- package/dist/app/chunks/html-F6FWTBSG.js +1 -0
- package/dist/app/chunks/java-ANIVV5H5.js +1 -0
- package/dist/app/chunks/javascript-IH374YWG.js +1 -0
- package/dist/app/chunks/json-WGGVVXSB.js +1 -0
- package/dist/app/chunks/jsx-OTXJQD32.js +1 -0
- package/dist/app/chunks/{latex-ZWK7SXB6.js → latex-QCZNITGN.js} +1 -1
- package/dist/app/chunks/lua-4CRZ64PU.js +1 -0
- package/dist/app/chunks/{nginx-2L6VRV3A.js → nginx-V4CYS7WR.js} +1 -1
- package/dist/app/chunks/{perl-PBUAXXK6.js → perl-BHTX7F5B.js} +1 -1
- package/dist/app/chunks/{php-QJX57PA6.js → php-AYGJ5WYZ.js} +1 -1
- package/dist/app/chunks/r-4YFCFA4B.js +1 -0
- package/dist/app/chunks/{ruby-RLOWMVPO.js → ruby-42NMJWAL.js} +1 -1
- package/dist/app/chunks/{scss-B5CAI3HR.js → scss-XD7VU2CU.js} +1 -1
- package/dist/app/chunks/sql-B55MH6JJ.js +1 -0
- package/dist/app/chunks/{svelte-GT3D4C44.js → svelte-WR3QPIJU.js} +1 -1
- package/dist/app/chunks/tsx-S73GAE2L.js +1 -0
- package/dist/app/chunks/typescript-NEWOAB5Z.js +1 -0
- package/dist/app/chunks/{vue-JOW62MHD.js → vue-CCDS4VZK.js} +1 -1
- package/dist/app/chunks/xml-UXKAGLDR.js +1 -0
- package/dist/app/chunks/yaml-UMWKDTK7.js +1 -0
- package/dist/app/host.js +40 -40
- package/dist/scripts/embeddingLockV3.d.ts +30 -0
- package/dist/scripts/embeddingLockV3.d.ts.map +1 -0
- package/dist/scripts/embeddingLockV3.js +100 -0
- package/dist/scripts/embeddingLockV3.js.map +1 -0
- package/dist/scripts/vectorProofV1.d.ts +90 -0
- package/dist/scripts/vectorProofV1.d.ts.map +1 -0
- package/dist/scripts/vectorProofV1.js +220 -0
- package/dist/scripts/vectorProofV1.js.map +1 -0
- package/dist/src/config/env.d.ts +21 -0
- package/dist/src/config/env.d.ts.map +1 -1
- package/dist/src/config/env.js +55 -0
- package/dist/src/config/env.js.map +1 -1
- package/dist/src/connectors/AppSourceConfigStore.d.ts +12 -2
- package/dist/src/connectors/AppSourceConfigStore.d.ts.map +1 -1
- package/dist/src/connectors/AppSourceConfigStore.js +48 -40
- package/dist/src/connectors/AppSourceConfigStore.js.map +1 -1
- package/dist/src/connectors/LocalDiskConnector.d.ts +11 -14
- package/dist/src/connectors/LocalDiskConnector.d.ts.map +1 -1
- package/dist/src/connectors/LocalDiskConnector.js +87 -107
- package/dist/src/connectors/LocalDiskConnector.js.map +1 -1
- package/dist/src/connectors/sourceConfigMutation.d.ts +11 -1
- package/dist/src/connectors/sourceConfigMutation.d.ts.map +1 -1
- package/dist/src/connectors/sourceConfigMutation.js +36 -3
- package/dist/src/connectors/sourceConfigMutation.js.map +1 -1
- package/dist/src/domain/chunker.js +8 -3
- package/dist/src/domain/chunker.js.map +1 -1
- package/dist/src/domain/healthTypes.d.ts +11 -0
- package/dist/src/domain/healthTypes.d.ts.map +1 -1
- package/dist/src/domain/mime.d.ts +1 -0
- package/dist/src/domain/mime.d.ts.map +1 -1
- package/dist/src/domain/mime.js +48 -0
- package/dist/src/domain/mime.js.map +1 -1
- package/dist/src/domain/pendingChange.d.ts +7 -0
- package/dist/src/domain/pendingChange.d.ts.map +1 -1
- package/dist/src/domain/pendingChange.js +11 -0
- package/dist/src/domain/pendingChange.js.map +1 -1
- package/dist/src/domain/sourceConfig.d.ts +7 -14
- package/dist/src/domain/sourceConfig.d.ts.map +1 -1
- package/dist/src/domain/sourceConfig.js +1 -13
- package/dist/src/domain/sourceConfig.js.map +1 -1
- package/dist/src/domain/sourcePackages.d.ts +11 -2
- package/dist/src/domain/sourcePackages.d.ts.map +1 -1
- package/dist/src/domain/syncTrigger.d.ts +2 -3
- package/dist/src/domain/syncTrigger.d.ts.map +1 -1
- package/dist/src/domain/syncTrigger.js +2 -3
- package/dist/src/domain/syncTrigger.js.map +1 -1
- package/dist/src/domain/syncTypes.d.ts +21 -4
- package/dist/src/domain/syncTypes.d.ts.map +1 -1
- package/dist/src/domain/syncTypes.js.map +1 -1
- package/dist/src/domain/text.d.ts +3 -0
- package/dist/src/domain/text.d.ts.map +1 -1
- package/dist/src/domain/text.js +5 -0
- package/dist/src/domain/text.js.map +1 -1
- package/dist/src/domain/types.d.ts +9 -0
- package/dist/src/domain/types.d.ts.map +1 -1
- package/dist/src/embeddings/clients/GeminiEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/GeminiEmbedder.js +11 -5
- package/dist/src/embeddings/clients/GeminiEmbedder.js.map +1 -1
- package/dist/src/embeddings/clients/OllamaEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/OllamaEmbedder.js +13 -6
- package/dist/src/embeddings/clients/OllamaEmbedder.js.map +1 -1
- package/dist/src/embeddings/clients/OpenAIEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/OpenAIEmbedder.js +12 -9
- package/dist/src/embeddings/clients/OpenAIEmbedder.js.map +1 -1
- package/dist/src/embeddings/clients/QwenEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/QwenEmbedder.js +12 -5
- package/dist/src/embeddings/clients/QwenEmbedder.js.map +1 -1
- package/dist/src/embeddings/clients/VoyageEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/VoyageEmbedder.js +12 -5
- package/dist/src/embeddings/clients/VoyageEmbedder.js.map +1 -1
- package/dist/src/embeddings/factory.d.ts +2 -0
- package/dist/src/embeddings/factory.d.ts.map +1 -1
- package/dist/src/embeddings/factory.js +23 -3
- package/dist/src/embeddings/factory.js.map +1 -1
- package/dist/src/embeddings/lock.d.ts +22 -11
- package/dist/src/embeddings/lock.d.ts.map +1 -1
- package/dist/src/embeddings/lock.js +47 -29
- package/dist/src/embeddings/lock.js.map +1 -1
- package/dist/src/embeddings/retry.d.ts +5 -0
- package/dist/src/embeddings/retry.d.ts.map +1 -1
- package/dist/src/embeddings/retry.js +35 -0
- package/dist/src/embeddings/retry.js.map +1 -1
- package/dist/src/events/pending.d.ts +56 -0
- package/dist/src/events/pending.d.ts.map +1 -0
- package/dist/src/events/pending.js +108 -0
- package/dist/src/events/pending.js.map +1 -0
- package/dist/src/events/source.d.ts +60 -14
- package/dist/src/events/source.d.ts.map +1 -1
- package/dist/src/events/source.js +149 -31
- package/dist/src/events/source.js.map +1 -1
- package/dist/src/events/vector.d.ts +33 -0
- package/dist/src/events/vector.d.ts.map +1 -0
- package/dist/src/events/vector.js +57 -0
- package/dist/src/events/vector.js.map +1 -0
- package/dist/src/git/GitRunner.d.ts.map +1 -1
- package/dist/src/git/GitRunner.js +11 -8
- package/dist/src/git/GitRunner.js.map +1 -1
- package/dist/src/git/sourceGitControl.d.ts.map +1 -1
- package/dist/src/git/sourceGitControl.js +2 -1
- package/dist/src/git/sourceGitControl.js.map +1 -1
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/lifecycle.d.ts +20 -4
- package/dist/src/lifecycle.d.ts.map +1 -1
- package/dist/src/lifecycle.js +5 -1
- package/dist/src/lifecycle.js.map +1 -1
- package/dist/src/routes/health.js +6 -1
- package/dist/src/routes/health.js.map +1 -1
- package/dist/src/routes/pending.d.ts.map +1 -1
- package/dist/src/routes/pending.js +2 -1
- package/dist/src/routes/pending.js.map +1 -1
- package/dist/src/routes/sources.d.ts.map +1 -1
- package/dist/src/routes/sources.js +60 -11
- package/dist/src/routes/sources.js.map +1 -1
- package/dist/src/services/BridgeService.d.ts +2 -2
- package/dist/src/services/BridgeService.d.ts.map +1 -1
- package/dist/src/services/BridgeService.js +11 -2
- package/dist/src/services/BridgeService.js.map +1 -1
- package/dist/src/services/FilesystemService.d.ts +3 -1
- package/dist/src/services/FilesystemService.d.ts.map +1 -1
- package/dist/src/services/FilesystemService.js +3 -1
- package/dist/src/services/FilesystemService.js.map +1 -1
- package/dist/src/services/FilesystemUiService.js +1 -1
- package/dist/src/services/FilesystemUiService.js.map +1 -1
- package/dist/src/services/SyncScheduler.d.ts.map +1 -1
- package/dist/src/services/SyncScheduler.js +0 -14
- package/dist/src/services/SyncScheduler.js.map +1 -1
- package/dist/src/services/VectorHealthService.d.ts.map +1 -1
- package/dist/src/services/VectorHealthService.js +2 -46
- package/dist/src/services/VectorHealthService.js.map +1 -1
- package/dist/src/services/baselineRepair.d.ts +2 -2
- package/dist/src/services/baselineRepair.js +2 -2
- package/dist/src/services/declaredSources.d.ts +8 -1
- package/dist/src/services/declaredSources.d.ts.map +1 -1
- package/dist/src/services/declaredSources.js +19 -19
- package/dist/src/services/declaredSources.js.map +1 -1
- package/dist/src/store/dataFormats.d.ts +14 -3
- package/dist/src/store/dataFormats.d.ts.map +1 -1
- package/dist/src/store/dataFormats.js +15 -5
- package/dist/src/store/dataFormats.js.map +1 -1
- package/dist/src/sync/DeltaSyncRunner.d.ts.map +1 -1
- package/dist/src/sync/DeltaSyncRunner.js +78 -243
- package/dist/src/sync/DeltaSyncRunner.js.map +1 -1
- package/dist/src/sync/SyncJobQueue.d.ts +33 -4
- package/dist/src/sync/SyncJobQueue.d.ts.map +1 -1
- package/dist/src/sync/SyncJobQueue.js +179 -19
- package/dist/src/sync/SyncJobQueue.js.map +1 -1
- package/dist/src/sync/moveDetector.d.ts +2 -1
- package/dist/src/sync/moveDetector.d.ts.map +1 -1
- package/dist/src/sync/moveDetector.js +2 -1
- package/dist/src/sync/moveDetector.js.map +1 -1
- package/dist/src/tools/fs_search.d.ts.map +1 -1
- package/dist/src/tools/fs_search.js +8 -0
- package/dist/src/tools/fs_search.js.map +1 -1
- package/dist/src/tools/fs_write.js +3 -1
- package/dist/src/tools/fs_write.js.map +1 -1
- package/dist/src/usecases/chunkUpsert.d.ts +4 -0
- package/dist/src/usecases/chunkUpsert.d.ts.map +1 -1
- package/dist/src/usecases/chunkUpsert.js +103 -60
- package/dist/src/usecases/chunkUpsert.js.map +1 -1
- package/dist/src/usecases/embeddingBacklog.d.ts +31 -0
- package/dist/src/usecases/embeddingBacklog.d.ts.map +1 -0
- package/dist/src/usecases/embeddingBacklog.js +244 -0
- package/dist/src/usecases/embeddingBacklog.js.map +1 -0
- package/dist/src/usecases/listSourcePackages.js +17 -1
- package/dist/src/usecases/listSourcePackages.js.map +1 -1
- package/dist/src/usecases/mediaLane.d.ts +11 -4
- package/dist/src/usecases/mediaLane.d.ts.map +1 -1
- package/dist/src/usecases/mediaLane.js +67 -30
- package/dist/src/usecases/mediaLane.js.map +1 -1
- package/dist/src/usecases/pendingReview.d.ts.map +1 -1
- package/dist/src/usecases/pendingReview.js +24 -7
- package/dist/src/usecases/pendingReview.js.map +1 -1
- package/dist/src/usecases/purgeExcludedPoints.d.ts +2 -0
- package/dist/src/usecases/purgeExcludedPoints.d.ts.map +1 -1
- package/dist/src/usecases/purgeExcludedPoints.js +3 -2
- package/dist/src/usecases/purgeExcludedPoints.js.map +1 -1
- package/dist/src/usecases/readFiles.d.ts.map +1 -1
- package/dist/src/usecases/readFiles.js +33 -6
- package/dist/src/usecases/readFiles.js.map +1 -1
- package/dist/src/usecases/reconcileSourcePackageRoots.d.ts +31 -0
- package/dist/src/usecases/reconcileSourcePackageRoots.d.ts.map +1 -0
- package/dist/src/usecases/reconcileSourcePackageRoots.js +114 -0
- package/dist/src/usecases/reconcileSourcePackageRoots.js.map +1 -0
- package/dist/src/usecases/searchFiles.d.ts.map +1 -1
- package/dist/src/usecases/searchFiles.js +62 -64
- package/dist/src/usecases/searchFiles.js.map +1 -1
- package/dist/src/usecases/syncIndexedFile.d.ts +42 -0
- package/dist/src/usecases/syncIndexedFile.d.ts.map +1 -0
- package/dist/src/usecases/syncIndexedFile.js +242 -0
- package/dist/src/usecases/syncIndexedFile.js.map +1 -0
- package/dist/src/usecases/syncPointWrites.d.ts +5 -0
- package/dist/src/usecases/syncPointWrites.d.ts.map +1 -1
- package/dist/src/usecases/syncPointWrites.js +32 -5
- package/dist/src/usecases/syncPointWrites.js.map +1 -1
- package/dist/src/usecases/uploadBinary.d.ts.map +1 -1
- package/dist/src/usecases/uploadBinary.js +36 -49
- package/dist/src/usecases/uploadBinary.js.map +1 -1
- package/dist/src/usecases/vector/endpointGuard.js +2 -1
- package/dist/src/usecases/vector/endpointGuard.js.map +1 -1
- package/dist/src/usecases/vector/estimate.d.ts +7 -4
- package/dist/src/usecases/vector/estimate.d.ts.map +1 -1
- package/dist/src/usecases/vector/estimate.js +8 -5
- package/dist/src/usecases/vector/estimate.js.map +1 -1
- package/dist/src/usecases/vector/rebuildGeneration.d.ts +29 -5
- package/dist/src/usecases/vector/rebuildGeneration.d.ts.map +1 -1
- package/dist/src/usecases/vector/rebuildGeneration.js +224 -82
- package/dist/src/usecases/vector/rebuildGeneration.js.map +1 -1
- package/dist/src/usecases/vectorMaintenance.d.ts +46 -4
- package/dist/src/usecases/vectorMaintenance.d.ts.map +1 -1
- package/dist/src/usecases/vectorMaintenance.js +190 -11
- package/dist/src/usecases/vectorMaintenance.js.map +1 -1
- package/dist/src/usecases/write/commit.d.ts +6 -8
- package/dist/src/usecases/write/commit.d.ts.map +1 -1
- package/dist/src/usecases/write/commit.js +96 -112
- package/dist/src/usecases/write/commit.js.map +1 -1
- package/dist/src/usecases/write/content.js +1 -2
- package/dist/src/usecases/write/content.js.map +1 -1
- package/dist/src/usecases/write/ports.d.ts +1 -0
- package/dist/src/usecases/write/ports.d.ts.map +1 -1
- package/dist/src/vector/FilesystemVectorIndex.d.ts +34 -3
- package/dist/src/vector/FilesystemVectorIndex.d.ts.map +1 -1
- package/dist/src/vector/FilesystemVectorIndex.js +254 -20
- package/dist/src/vector/FilesystemVectorIndex.js.map +1 -1
- package/dist/src/vector/InMemoryVectorIndex.d.ts +9 -2
- package/dist/src/vector/InMemoryVectorIndex.d.ts.map +1 -1
- package/dist/src/vector/InMemoryVectorIndex.js +25 -6
- package/dist/src/vector/InMemoryVectorIndex.js.map +1 -1
- package/dist/src/vector/QdrantVectorIndex.d.ts +35 -9
- package/dist/src/vector/QdrantVectorIndex.d.ts.map +1 -1
- package/dist/src/vector/QdrantVectorIndex.js +209 -90
- package/dist/src/vector/QdrantVectorIndex.js.map +1 -1
- package/dist/src/vector/qdrantFilter.d.ts +3 -1
- package/dist/src/vector/qdrantFilter.d.ts.map +1 -1
- package/dist/src/vector/qdrantFilter.js +2 -0
- package/dist/src/vector/qdrantFilter.js.map +1 -1
- package/dist/src/vector/types.d.ts +81 -0
- package/dist/src/vector/types.d.ts.map +1 -1
- package/dist/src/vector/types.js +2 -0
- package/dist/src/vector/types.js.map +1 -1
- package/dist/src/wiring.d.ts +6 -0
- package/dist/src/wiring.d.ts.map +1 -1
- package/dist/src/wiring.js +62 -3
- package/dist/src/wiring.js.map +1 -1
- package/dist/tools/fs_search.json +2 -2
- package/docs/drive-overview.md +2 -2
- package/instructions/brain-model.md +1 -1
- package/package.json +999 -10
- package/rules/brain-rules.md +1 -1
- package/skills/connect-business-data/SKILL.md +6 -4
- package/skills/create-package/SKILL.md +14 -12
- package/skills/create-package/references/config-and-credentials.md +3 -3
- package/skills/create-package/references/connectors-and-uri-policies.md +5 -3
- package/skills/create-package/references/manifest-and-discovery.md +4 -3
- package/skills/create-package/references/runtime-and-build.md +9 -9
- package/skills/create-package/references/skills-and-context.md +3 -3
- package/skills/create-package/references/surfaces-and-design.md +2 -1
- package/skills/create-package/references/team-and-workflows.md +2 -2
- package/skills/create-package/references/test-and-ship.md +15 -15
- package/skills/create-package/references/tools-and-schemas.md +1 -1
- package/skills/create-package/scripts/check-package.sh +3 -3
- package/skills/create-package/scripts/verify-loaded.sh +16 -16
- package/skills/manage-files/SKILL.md +2 -2
- package/skills/manage-sources/SKILL.md +16 -15
- package/skills/manage-sources/scripts/reembed.sh +9 -3
- package/skills/sync/SKILL.md +4 -4
- package/tools/fs_search.json +2 -2
- package/dist/app/chunks/bash-M45GHPBQ.js +0 -1
- package/dist/app/chunks/c-X3REOAWD.js +0 -1
- package/dist/app/chunks/cpp-VOWI743W.js +0 -1
- package/dist/app/chunks/css-2ZVPHE25.js +0 -1
- package/dist/app/chunks/graphql-7V2NP54C.js +0 -1
- package/dist/app/chunks/html-UF3LFLNA.js +0 -1
- package/dist/app/chunks/java-PNTAU4L3.js +0 -1
- package/dist/app/chunks/javascript-4JJTQGEL.js +0 -1
- package/dist/app/chunks/json-VLQ6D3JH.js +0 -1
- package/dist/app/chunks/jsx-KQKSLAAH.js +0 -1
- package/dist/app/chunks/lua-UFPYUJ2O.js +0 -1
- package/dist/app/chunks/r-QEZB45SR.js +0 -1
- package/dist/app/chunks/sql-DTZHNUZP.js +0 -1
- package/dist/app/chunks/tsx-WPR2WRZD.js +0 -1
- package/dist/app/chunks/typescript-LWO362OA.js +0 -1
- package/dist/app/chunks/xml-A5H72RTS.js +0 -1
- package/dist/app/chunks/yaml-MXHSIJCI.js +0 -1
- package/dist/src/services/purgePendingNodes.d.ts +0 -32
- package/dist/src/services/purgePendingNodes.d.ts.map +0 -1
- package/dist/src/services/purgePendingNodes.js +0 -61
- package/dist/src/services/purgePendingNodes.js.map +0 -1
- /package/dist/app/chunks/{chunk-737Y24HS.js → chunk-3JASFUPF.js} +0 -0
- /package/dist/app/chunks/{chunk-DKD2L57N.js → chunk-6EYLOQ4F.js} +0 -0
- /package/dist/app/chunks/{chunk-KYH5EHHY.js → chunk-7YRZ5LOQ.js} +0 -0
- /package/dist/app/chunks/{chunk-6TC3DVWV.js → chunk-C7LU4RID.js} +0 -0
- /package/dist/app/chunks/{chunk-TY33BMKR.js → chunk-CD52YDZP.js} +0 -0
- /package/dist/app/chunks/{chunk-OLEIKJ3X.js → chunk-DCJKTCJ5.js} +0 -0
- /package/dist/app/chunks/{chunk-QLBZ4JR2.js → chunk-EXIU2KBV.js} +0 -0
- /package/dist/app/chunks/{chunk-SFUNBBEA.js → chunk-FDP4LO4P.js} +0 -0
- /package/dist/app/chunks/{chunk-BGRVFATT.js → chunk-HNETUS7U.js} +0 -0
- /package/dist/app/chunks/{chunk-4QNMHAIR.js → chunk-LBFZJEDR.js} +0 -0
- /package/dist/app/chunks/{chunk-TRRUHDPN.js → chunk-ULRXL5VM.js} +0 -0
- /package/dist/app/chunks/{chunk-S6B4WWIF.js → chunk-XJK47SMJ.js} +0 -0
- /package/dist/app/chunks/{chunk-DZ5C5FKI.js → chunk-YYAAIAU2.js} +0 -0
- /package/dist/app/chunks/{clojure-OC6KPSJO.js → clojure-4QCN4GHG.js} +0 -0
- /package/dist/app/chunks/{core-KGE7TQJ3.js → core-ZHTX3P2Q.js} +0 -0
- /package/dist/app/chunks/{csharp-E7VIFITL.js → csharp-HQWR4N52.js} +0 -0
- /package/dist/app/chunks/{dark-plus-BOSCZXE4.js → dark-plus-GYPKKZES.js} +0 -0
- /package/dist/app/chunks/{dart-6Y3X27SZ.js → dart-FO4TTVO2.js} +0 -0
- /package/dist/app/chunks/{diff-TR2NPA64.js → diff-SHANV4YC.js} +0 -0
- /package/dist/app/chunks/{dist-T32U2VVR.js → dist-WSDF34DM.js} +0 -0
- /package/dist/app/chunks/{docker-N33HOW54.js → docker-VX55HTDS.js} +0 -0
- /package/dist/app/chunks/{go-6DGG5UTP.js → go-5UCPEC6S.js} +0 -0
- /package/dist/app/chunks/{haskell-KK36PMMM.js → haskell-25PJ3OSG.js} +0 -0
- /package/dist/app/chunks/{ini-VHVQ5TPB.js → ini-BJIVD5MK.js} +0 -0
- /package/dist/app/chunks/{kotlin-44WS3JHE.js → kotlin-MNR6XOEQ.js} +0 -0
- /package/dist/app/chunks/{less-5WZ3KLAK.js → less-KA5JURX6.js} +0 -0
- /package/dist/app/chunks/{light-plus-HGGBC43G.js → light-plus-QXH2UUWZ.js} +0 -0
- /package/dist/app/chunks/{make-X7RXMVW7.js → make-LPD5KOCO.js} +0 -0
- /package/dist/app/chunks/{markdown-46IKUAIB.js → markdown-LMWF6MQR.js} +0 -0
- /package/dist/app/chunks/{mdx-DGLHDAO7.js → mdx-WPMNN5BS.js} +0 -0
- /package/dist/app/chunks/{nix-Q6FYQZLX.js → nix-QGWLUUMZ.js} +0 -0
- /package/dist/app/chunks/{powershell-L4KJJ6DK.js → powershell-7GSBBEFF.js} +0 -0
- /package/dist/app/chunks/{prisma-IKS4LVH2.js → prisma-FPMPQISO.js} +0 -0
- /package/dist/app/chunks/{proto-AYCDOTJO.js → proto-7KDX5Z2U.js} +0 -0
- /package/dist/app/chunks/{python-KXMCSAFQ.js → python-CFG4FTQW.js} +0 -0
- /package/dist/app/chunks/{rust-J3BC3P6V.js → rust-X7A37WU3.js} +0 -0
- /package/dist/app/chunks/{scala-AYIIHBJK.js → scala-OYHJSUG2.js} +0 -0
- /package/dist/app/chunks/{swift-QPHHCQD3.js → swift-PE7VNYW5.js} +0 -0
- /package/dist/app/chunks/{toml-UV7JK7K7.js → toml-ETQRDILP.js} +0 -0
|
@@ -4,7 +4,7 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|
|
4
4
|
import { SlidersHorizontal } from 'lucide-react';
|
|
5
5
|
import { useFilesystemStore } from '../store/filesystemStore';
|
|
6
6
|
import { folderUri } from '../store/helpers';
|
|
7
|
-
import { grepScopeFor, type SearchUiMode } from '../store/searchSlice';
|
|
7
|
+
import { grepScopeFor, type SearchEffect, type SearchUiMode } from '../store/searchSlice';
|
|
8
8
|
|
|
9
9
|
const MODES: Array<{ id: SearchUiMode; label: string; hint: string }> = [
|
|
10
10
|
{ id: 'keyword', label: 'Keyword', hint: 'Indexed word search (primary)' },
|
|
@@ -20,6 +20,19 @@ const filterInputClass =
|
|
|
20
20
|
* input itself lives in the sidebar HEADER row (FilesystemWidget) — this
|
|
21
21
|
* panel renders only while `sidebarView === 'search'`.
|
|
22
22
|
*/
|
|
23
|
+
/**
|
|
24
|
+
* The one line that says a search did not run as asked: a semantic request
|
|
25
|
+
* answered by keyword search because embedding is unavailable (the reason is
|
|
26
|
+
* the server's), or the server's own warning. `null` when it ran as asked.
|
|
27
|
+
*/
|
|
28
|
+
export function searchEffectLine(effect: SearchEffect | undefined): string | null {
|
|
29
|
+
if (!effect) return null;
|
|
30
|
+
if (effect.requestedMode === 'semantic' && effect.effectiveMode && effect.effectiveMode !== 'semantic') {
|
|
31
|
+
return `Searched as keyword — semantic search unavailable${effect.embeddingDegraded ? ` (${effect.embeddingDegraded})` : ''}. Use specific words.`;
|
|
32
|
+
}
|
|
33
|
+
return effect.warning ?? null;
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
export function FileTreeSearch({ agentId }: { agentId: string }) {
|
|
24
37
|
const projectId = useFilesystemStore((s) => s.activeProjectId);
|
|
25
38
|
const setActiveFolderPath = useFilesystemStore((s) => s.setActiveFolderPath);
|
|
@@ -32,6 +45,8 @@ export function FileTreeSearch({ agentId }: { agentId: string }) {
|
|
|
32
45
|
const searchResults = useFilesystemStore((s) => s.searchResults);
|
|
33
46
|
const searchLoading = useFilesystemStore((s) => s.searchLoading);
|
|
34
47
|
const searchError = useFilesystemStore((s) => s.searchError);
|
|
48
|
+
const searchEffect = useFilesystemStore((s) => s.searchEffect);
|
|
49
|
+
const effectLine = searchEffectLine(searchEffect);
|
|
35
50
|
const runSearch = useFilesystemStore((s) => s.runSearch);
|
|
36
51
|
const activeFolderPath = useFilesystemStore((s) => s.activeFolderPath);
|
|
37
52
|
const sources = useFilesystemStore((s) => s.sources);
|
|
@@ -185,9 +200,15 @@ export function FileTreeSearch({ agentId }: { agentId: string }) {
|
|
|
185
200
|
) : searchError ? (
|
|
186
201
|
<div className="px-2 py-3 text-[11px] text-red-300/70">{searchError}</div>
|
|
187
202
|
) : searchResults.length === 0 ? (
|
|
188
|
-
<div className="px-2 py-3 text-[11px] text-white/35">
|
|
203
|
+
<div className="px-2 py-3 text-[11px] text-white/35">
|
|
204
|
+
{effectLine ? <div role="status" className="mb-1 text-[10px] text-amber-200/70">{effectLine}</div> : null}
|
|
205
|
+
No matches
|
|
206
|
+
</div>
|
|
189
207
|
) : (
|
|
190
208
|
<div className="space-y-1">
|
|
209
|
+
{effectLine ? (
|
|
210
|
+
<div role="status" className="px-2 py-1 text-[10px] text-amber-200/70">{effectLine}</div>
|
|
211
|
+
) : null}
|
|
191
212
|
{searchResults.map((result) => (
|
|
192
213
|
<button
|
|
193
214
|
key={`${result.id}:${result.uri}:${result.chunk_start ?? ''}`}
|
|
@@ -203,6 +224,12 @@ export function FileTreeSearch({ agentId }: { agentId: string }) {
|
|
|
203
224
|
{typeof result.score === 'number' ? (
|
|
204
225
|
<span className="text-[9px] text-white/30 shrink-0">{result.score.toFixed(2)}</span>
|
|
205
226
|
) : null}
|
|
227
|
+
{result.embedding_status === 'missing' || result.embedding_status === 'stale' ? (
|
|
228
|
+
<span className="text-[9px] px-1 py-0.5 rounded bg-white/[0.06] text-white/40 shrink-0"
|
|
229
|
+
title="This file is text-searchable; its semantic embedding is not current">
|
|
230
|
+
embedding {result.embedding_status}
|
|
231
|
+
</span>
|
|
232
|
+
) : null}
|
|
206
233
|
{result.match_type ? (
|
|
207
234
|
<span className="text-[9px] px-1 py-0.5 rounded bg-white/[0.06] text-white/40 shrink-0">
|
|
208
235
|
{result.match_type === 'hybrid' ? 'H' : result.match_type === 'semantic' ? 'S' : 'K'}
|
|
@@ -98,17 +98,22 @@ export function CurrentGitDiffs({ uri, rows: gitRows, agentId, projectId, canWri
|
|
|
98
98
|
</div>)}</React.Fragment>)}</>;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/** The lane key one card's answer belongs to: the record and its source revision. */
|
|
102
|
+
const previewKey = (rec: PendingChangeRecord, revision: number): string => `preview\u0000${rec.id}\u0000${rec.updatedAt}\u0000${revision}`;
|
|
103
|
+
|
|
101
104
|
const previewBytes = (p: PendingPreview): number =>
|
|
102
105
|
2 * ((p.baseline.state === 'text' ? p.baseline.text.length : 0) + (p.current.state === 'text' ? p.current.text.length : 0)) + 256;
|
|
103
106
|
|
|
104
107
|
|
|
105
108
|
export const PendingDiffCard = memo(function PendingDiffCard({
|
|
106
|
-
rec, agentId, projectId, focused, orphaned, onOpen, onApprove, onRevert, onDiscard, onDismiss, onFocus, busy, queue, revision, canWrite, onPreview, peekPreview, extra,
|
|
109
|
+
rec, agentId, projectId, focused, orphaned, onOpen, onApprove, onRevert, onDiscard, onDismiss, onFocus, busy, queue, revision, canWrite, onPreview, peekPreview, takeRefetch, extra,
|
|
107
110
|
}: {
|
|
108
111
|
rec: PendingChangeRecord; agentId: string; projectId: string | null; focused: boolean; orphaned: boolean;
|
|
109
112
|
onOpen: (uri: string) => void; onApprove: () => void; onRevert: () => void; onDiscard: () => void; onDismiss: () => void;
|
|
110
113
|
onFocus: () => void; busy: boolean; queue: VisibleDiffQueue; revision: number; canWrite: boolean;
|
|
111
|
-
onPreview: (preview: PendingPreview) => void; peekPreview: (id: string) => PendingPreview | undefined;
|
|
114
|
+
onPreview: (preview: PendingPreview) => void; peekPreview: (id: string) => PendingPreview | undefined;
|
|
115
|
+
/** The dialog's post-409 refetch: `'wait'` while in flight, then its answer ONCE for the key it was taken for. */
|
|
116
|
+
takeRefetch: (id: string, key: string) => PendingPreview | 'wait' | undefined; extra?: React.ReactNode;
|
|
112
117
|
}) {
|
|
113
118
|
// Cards open inline by default — the diff is the review unit.
|
|
114
119
|
const [expanded, setExpanded] = useState(true);
|
|
@@ -126,11 +131,14 @@ export const PendingDiffCard = memo(function PendingDiffCard({
|
|
|
126
131
|
active.current = expanded && shown;
|
|
127
132
|
// The record + source revision this card's answer belongs to; leaving the viewport
|
|
128
133
|
// keeps it (the lane cache answers a re-entry).
|
|
129
|
-
const cacheKey =
|
|
134
|
+
const cacheKey = previewKey(rec, revision);
|
|
130
135
|
const loadedKey = useRef('');
|
|
131
136
|
useEffect(() => {
|
|
132
137
|
const generation = ++seq.current;
|
|
133
138
|
if (!expanded || !shown || loadedKey.current === cacheKey) { setLoading(false); return; }
|
|
139
|
+
const adopted = takeRefetch(rec.id, cacheKey);
|
|
140
|
+
if (adopted === 'wait') { setLoading(true); return; }
|
|
141
|
+
if (adopted) { setPreview(adopted); setLoadError(null); setAnswerKey(cacheKey); loadedKey.current = cacheKey; setLoading(false); return; }
|
|
134
142
|
setLoading(true);
|
|
135
143
|
void queue.run(() => previewPendingChange({ agentId, ...(projectId ? { projectId } : {}), id: rec.id }),
|
|
136
144
|
() => generation === seq.current && active.current, { key: cacheKey, bytes: previewBytes }).then(next => {
|
|
@@ -207,7 +215,14 @@ export function PendingChangesPanel({ agentId, projectId }: Props) {
|
|
|
207
215
|
const host = useFilesystemHost();
|
|
208
216
|
const canWrite = hasFeature({ grantedFeatures: [...(host.grantedFeatures ?? [])] }, 'drive.write');
|
|
209
217
|
const sources = useFilesystemStore(s => s.sources);
|
|
210
|
-
const
|
|
218
|
+
const liveRevisions = useFilesystemStore(s => s.sourceChangeRevision);
|
|
219
|
+
// A review batch (the sweep, a dialog group) HOLDS the revisions its own writes bump: one re-list
|
|
220
|
+
// at its end (`afterMutation`) instead of a re-list and every visible card's re-preview per write.
|
|
221
|
+
const [held, setHeld] = useState<typeof liveRevisions | null>(null);
|
|
222
|
+
const revisions = held ?? liveRevisions;
|
|
223
|
+
const liveRef = useRef(liveRevisions); liveRef.current = liveRevisions;
|
|
224
|
+
const listedRevisions = useRef(liveRevisions);
|
|
225
|
+
const holdRevisions = useCallback(() => setHeld(prev => prev ?? liveRef.current), []);
|
|
211
226
|
const pendingPanelFilter = useFilesystemStore((s) => s.pendingPanelFilter);
|
|
212
227
|
const setPendingPanelFilter = useFilesystemStore((s) => s.setPendingPanelFilter);
|
|
213
228
|
const openFile = useFilesystemStore((s) => s.openFile);
|
|
@@ -220,6 +235,16 @@ export function PendingChangesPanel({ agentId, projectId }: Props) {
|
|
|
220
235
|
const queue = useVisibleDiffCards(scrollRoot);
|
|
221
236
|
const previews = useRef(new Map<string, PendingPreview>());
|
|
222
237
|
const peekPreview = useCallback((id: string) => previews.current.get(id), []);
|
|
238
|
+
// ONE fetch per dialog 409: the dialog's refetch waits for the re-list; the record's card
|
|
239
|
+
// holds (`null`) until it lands, then adopts it under the key the final nonce gives it.
|
|
240
|
+
const refetches = useRef(new Map<string, { key: string; preview: PendingPreview } | null>());
|
|
241
|
+
const relisted = useRef(Promise.withResolvers<PendingChangeRecord[] | undefined>());
|
|
242
|
+
const takeRefetch = useCallback((id: string, key: string): PendingPreview | 'wait' | undefined => {
|
|
243
|
+
const entry = refetches.current.get(id);
|
|
244
|
+
if (entry === null) return 'wait';
|
|
245
|
+
if (entry) refetches.current.delete(id);
|
|
246
|
+
return entry?.key === key ? entry.preview : undefined;
|
|
247
|
+
}, []);
|
|
223
248
|
const [filters, setFilters] = useState<ChangeFilters>(NO_FILTERS);
|
|
224
249
|
const setFilter = (key: keyof ChangeFilters) => (event: React.ChangeEvent<HTMLSelectElement>) =>
|
|
225
250
|
setFilters(f => ({ ...f, [key]: event.target.value }));
|
|
@@ -229,7 +254,21 @@ export function PendingChangesPanel({ agentId, projectId }: Props) {
|
|
|
229
254
|
const [loading, setLoading] = useState(false);
|
|
230
255
|
const [error, setError] = useState<string | null>(null);
|
|
231
256
|
const [busyId, setBusyId] = useState<string | null>(null);
|
|
232
|
-
|
|
257
|
+
// Per record: a refused review refetches only ITS card's preview.
|
|
258
|
+
const [nonces, setNonces] = useState<Readonly<Record<string, number>>>({});
|
|
259
|
+
const latest = useRef({ nonces, revisions }); latest.current = { nonces, revisions };
|
|
260
|
+
const refetch = useCallback(async (rec: PendingChangeRecord): Promise<PendingPreview> => {
|
|
261
|
+
refetches.current.set(rec.id, null);
|
|
262
|
+
try {
|
|
263
|
+
const listed = (await relisted.current.promise)?.find(r => r.id === rec.id) ?? rec;
|
|
264
|
+
const fresh = await previewPendingChange({ agentId, ...(projectId ? { projectId } : {}), id: rec.id });
|
|
265
|
+
const revision = (latest.current.nonces[rec.id] ?? 0) + 1 + (latest.current.revisions[listed.sourceSlug] ?? 0);
|
|
266
|
+
refetches.current.set(rec.id, { key: previewKey(listed, revision), preview: fresh });
|
|
267
|
+
previews.current.set(rec.id, fresh);
|
|
268
|
+
return fresh;
|
|
269
|
+
} catch (failure) { refetches.current.delete(rec.id); throw failure; }
|
|
270
|
+
finally { setNonces(prev => ({ ...prev, [rec.id]: (prev[rec.id] ?? 0) + 1 })); }
|
|
271
|
+
}, [agentId, projectId]);
|
|
233
272
|
const [focusIndex, setFocusIndex] = useState(0);
|
|
234
273
|
const [review, setReview] = useState<ReviewRequest | null>(null);
|
|
235
274
|
const [results, setResults] = useState<ReviewResult[]>([]);
|
|
@@ -277,6 +316,7 @@ export function PendingChangesPanel({ agentId, projectId }: Props) {
|
|
|
277
316
|
const firstRevision = useRef(true);
|
|
278
317
|
useEffect(() => {
|
|
279
318
|
if (firstRevision.current) { firstRevision.current = false; return; }
|
|
319
|
+
if (revisions === listedRevisions.current) return;
|
|
280
320
|
void fetchPendingRef.current(false);
|
|
281
321
|
}, [revisions]);
|
|
282
322
|
|
|
@@ -291,7 +331,11 @@ export function PendingChangesPanel({ agentId, projectId }: Props) {
|
|
|
291
331
|
const groupsVisible = useMemo(() => visibleGitGroups(gitGroups, filters), [gitGroups, filters]);
|
|
292
332
|
const gitRows = useMemo(() => currentGitRows(groupsVisible), [groupsVisible]);
|
|
293
333
|
const items = useMemo(() => buildChangeItems(visible, gitRows, filters.kind, view), [visible, gitRows, filters.kind, view]);
|
|
294
|
-
|
|
334
|
+
// A landed preview that needs an acknowledgement moves its record into the count (no fetch of its own).
|
|
335
|
+
const [ackPreviews, setAckPreviews] = useState(0);
|
|
336
|
+
const { approvable, needsAck } = useMemo(() => splitApprovable(visible, orphanIds, REVIEW_BATCH, peekPreview),
|
|
337
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `ackPreviews` re-reads the preview map
|
|
338
|
+
[visible, orphanIds, peekPreview, ackPreviews]);
|
|
295
339
|
// The records one review session already acted on: the next batch starts after them,
|
|
296
340
|
// so a refused record does not come straight back.
|
|
297
341
|
const reviewed = useRef(new Set<string>());
|
|
@@ -336,11 +380,16 @@ export function PendingChangesPanel({ agentId, projectId }: Props) {
|
|
|
336
380
|
for (const result of done) {
|
|
337
381
|
const rec = changes.find(c => c.id === result.id);
|
|
338
382
|
if (rec && result.outcome === 'ok' && result.action !== 'revert') dropResolvedDeleteRow(rec);
|
|
339
|
-
|
|
383
|
+
// A `needs-ack` answer came from a still-valid preview: it stays cached (the header counts it).
|
|
384
|
+
if (result.outcome !== 'ok' && result.outcome !== 'needs-ack' && !refetches.current.has(result.id)) previews.current.delete(result.id);
|
|
340
385
|
}
|
|
341
|
-
|
|
386
|
+
const refused = done.filter(r => r.outcome !== 'ok' && r.outcome !== 'needs-ack' && !refetches.current.has(r.id));
|
|
387
|
+
if (refused.length) setNonces(prev => { const next = { ...prev }; for (const r of refused) next[r.id] = (next[r.id] ?? 0) + 1; return next; });
|
|
342
388
|
setResults(done);
|
|
343
|
-
await fetchPending(false);
|
|
389
|
+
const after = await fetchPending(false);
|
|
390
|
+
// The STORE's value, not the last render's: the batch's own bumps may not have rendered yet.
|
|
391
|
+
listedRevisions.current = useFilesystemStore.getState().sourceChangeRevision; setHeld(null);
|
|
392
|
+
relisted.current.resolve(after); relisted.current = Promise.withResolvers();
|
|
344
393
|
void refreshPendingCount({ agentId, projectId });
|
|
345
394
|
}, [changes, dropResolvedDeleteRow, fetchPending, refreshPendingCount, agentId, projectId]);
|
|
346
395
|
|
|
@@ -388,9 +437,10 @@ export function PendingChangesPanel({ agentId, projectId }: Props) {
|
|
|
388
437
|
}, [confirmRepair, repair]); useEffect(() => () => { if (repairTimer.current) clearTimeout(repairTimer.current); }, []);
|
|
389
438
|
|
|
390
439
|
// Approve the visible records whose own fields need no acknowledgement (two-step
|
|
391
|
-
// confirm). A record whose fresh preview turns out to need one is reported
|
|
440
|
+
// confirm). A record whose fresh preview turns out to need one is reported AND handed
|
|
441
|
+
// to the grouped review dialog, where it is approved or dismissed with its acknowledgement.
|
|
392
442
|
const approveAll = useCallback(async () => {
|
|
393
|
-
setApprovingAll(true); setConfirmAll(false); setError(null);
|
|
443
|
+
setApprovingAll(true); setConfirmAll(false); setError(null); holdRevisions();
|
|
394
444
|
const done: ReviewResult[] = [];
|
|
395
445
|
try {
|
|
396
446
|
for (const rec of approvable) {
|
|
@@ -398,12 +448,19 @@ export function PendingChangesPanel({ agentId, projectId }: Props) {
|
|
|
398
448
|
if (!preview) {
|
|
399
449
|
try { preview = await previewPendingChange({ agentId, ...(projectId ? { projectId } : {}), id: rec.id }); }
|
|
400
450
|
catch (e) { done.push({ id: rec.id, uri: rec.uri, action: 'approve', outcome: 'error', detail: e instanceof Error ? e.message : 'Preview failed' }); continue; }
|
|
451
|
+
previews.current.set(rec.id, preview);
|
|
401
452
|
}
|
|
402
453
|
if (preview.requiresAcknowledgement.length) { done.push({ id: rec.id, uri: rec.uri, action: 'approve', outcome: 'needs-ack' }); continue; }
|
|
403
454
|
done.push(await applyReview({ agentId, projectId }, rec, preview, 'approve', false));
|
|
404
455
|
}
|
|
405
|
-
} finally {
|
|
406
|
-
|
|
456
|
+
} finally {
|
|
457
|
+
setApprovingAll(false);
|
|
458
|
+
const handed = approvable.filter(rec => done.some(r => r.id === rec.id && r.outcome === 'needs-ack'));
|
|
459
|
+
if (handed.length) setAckPreviews(n => n + 1);
|
|
460
|
+
await afterMutation(done);
|
|
461
|
+
if (handed.length && canWrite) { reviewed.current.clear(); setReview({ kind: 'group', records: handed.slice(0, REVIEW_BATCH), total: handed.length }); }
|
|
462
|
+
}
|
|
463
|
+
}, [approvable, agentId, projectId, afterMutation, canWrite]);
|
|
407
464
|
|
|
408
465
|
const move = useCallback((delta: number) => setFocusIndex((i) => (visible.length === 0 ? 0
|
|
409
466
|
: Math.max(0, Math.min(visible.length - 1, i + delta)))), [visible.length]);
|
|
@@ -437,8 +494,8 @@ export function PendingChangesPanel({ agentId, projectId }: Props) {
|
|
|
437
494
|
if (item.kind === 'pending-card') {
|
|
438
495
|
return <PendingDiffCard rec={rec} agentId={agentId} projectId={projectId} focused={index === focusIndex} orphaned={orphaned}
|
|
439
496
|
onOpen={openInTree} {...handlers} onFocus={() => setFocusIndex(index)} busy={busyId === rec.id} queue={queue}
|
|
440
|
-
revision={
|
|
441
|
-
onPreview={preview => { previews.current.set(rec.id, preview); }} peekPreview={peekPreview}
|
|
497
|
+
revision={(nonces[rec.id] ?? 0) + (revisions[rec.sourceSlug] ?? 0)} canWrite={canWrite}
|
|
498
|
+
onPreview={preview => { previews.current.set(rec.id, preview); if (preview.requiresAcknowledgement.length) setAckPreviews(n => n + 1); }} peekPreview={peekPreview} takeRefetch={takeRefetch}
|
|
442
499
|
extra={filters.kind === 'all' ? <CurrentGitDiffs uri={rec.uri} rows={gitRows} agentId={agentId} projectId={projectId} canWrite={canWrite} revisions={revisions} queue={queue} /> : null} />;
|
|
443
500
|
}
|
|
444
501
|
return <div onMouseDown={() => setFocusIndex(index)} className={`group h-8 flex items-center gap-2 px-2 rounded-lg ${
|
|
@@ -461,7 +518,7 @@ export function PendingChangesPanel({ agentId, projectId }: Props) {
|
|
|
461
518
|
{canWrite && review ? <PendingReviewDialog request={review} agentId={agentId} projectId={projectId} run={queue.run}
|
|
462
519
|
key={review.kind === 'group' ? `group\u0000${review.records[0]?.id ?? ''}` : `single\u0000${review.rec.id}`}
|
|
463
520
|
onClose={() => setReview(null)} next={review.kind === 'group' ? unreviewed.filter(rec => !review.records.some(r => r.id === rec.id)).length : 0}
|
|
464
|
-
onNext={openReviewBatch}
|
|
521
|
+
onNext={openReviewBatch} refetch={refetch} onBatchStart={holdRevisions}
|
|
465
522
|
onDone={done => { for (const result of done) reviewed.current.add(result.id); void afterMutation(done); }} /> : null}
|
|
466
523
|
{/* Header: title + nav cursor + approve-all + view toggle + refresh */}
|
|
467
524
|
<div className="flex flex-wrap items-center gap-1.5 px-3 py-2 border-b border-white/5 shrink-0">
|
|
@@ -548,7 +605,7 @@ export function PendingChangesPanel({ agentId, projectId }: Props) {
|
|
|
548
605
|
<div className="mx-3 mt-2 max-h-28 overflow-auto overlay-scroll rounded-lg border border-amber-500/25 bg-amber-500/[0.06] px-2.5 py-1.5 text-[10.5px] text-amber-100/85 shrink-0">
|
|
549
606
|
<div className="flex items-center gap-2 font-semibold">{results.length - failed.length} done · {failed.length} not done
|
|
550
607
|
<button type="button" onClick={() => setResults([])} className="ml-auto rounded hover:bg-white/10" title="Dismiss"><X className="w-3 h-3" /></button></div>
|
|
551
|
-
{failed.map(r => <div key={r.id} className="truncate font-mono" title={r.detail}>{r.action} {r.uri}: {r.outcome === 'changed' ? 'changed since the preview (409)' : r.outcome === 'needs-ack' ? 'needs confirmation' : r.detail ?? 'refused'}</div>)}
|
|
608
|
+
{failed.map(r => <div key={r.id} className="truncate font-mono" title={r.detail}>{r.action} {r.uri}: {r.outcome === 'changed' ? 'changed since the preview (409)' : r.outcome === 'needs-ack' ? 'needs confirmation — opened in the review dialog' : r.detail ?? 'refused'}</div>)}
|
|
552
609
|
</div>
|
|
553
610
|
) : null}
|
|
554
611
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* per record, a refused revert included — nothing fails silently.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
11
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
12
|
import { createPortal } from 'react-dom';
|
|
13
13
|
import { Check, CheckCheck, Loader2, ShieldAlert, Undo2, X, XCircle } from 'lucide-react';
|
|
14
14
|
import {
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { DiffView } from '../../cards/DiffView';
|
|
19
19
|
import { detectLanguage } from '../../cards/diffUtils';
|
|
20
20
|
import type { VisibleDiffQueue } from '../hooks/useVisibleDiffCards';
|
|
21
|
-
import { classifyReviewOutcome, hunksOf, type ReviewOutcome } from './pendingChangesModel';
|
|
21
|
+
import { acceptsAbsence, classifyReviewOutcome, hunksOf, type ReviewOutcome } from './pendingChangesModel';
|
|
22
22
|
|
|
23
23
|
export type ReviewAction = 'approve' | 'dismiss' | 'revert';
|
|
24
24
|
export type ReviewRequest =
|
|
@@ -34,7 +34,7 @@ const REASONS: Record<Reason, { label: string; chip: string }> = {
|
|
|
34
34
|
};
|
|
35
35
|
const OUTCOME: Record<ReviewOutcome, { label: string; tone: string }> = {
|
|
36
36
|
ok: { label: 'Done', tone: 'text-emerald-300' },
|
|
37
|
-
changed: { label: 'Changed since the preview —
|
|
37
|
+
changed: { label: 'Changed since the preview — check the fresh state and confirm again', tone: 'text-amber-300' },
|
|
38
38
|
'needs-ack': { label: 'Still needs a confirmation', tone: 'text-amber-300' },
|
|
39
39
|
error: { label: 'Refused', tone: 'text-rose-300' },
|
|
40
40
|
};
|
|
@@ -62,6 +62,12 @@ export async function applyReview(params: { agentId: string; projectId: string |
|
|
|
62
62
|
|
|
63
63
|
function PreviewBody({ rec, preview }: { rec: PendingChangeRecord; preview: PendingPreview }) {
|
|
64
64
|
const hunks = useMemo(() => hunksOf(preview), [preview]);
|
|
65
|
+
if (acceptsAbsence(rec, preview)) {
|
|
66
|
+
return <div className="px-2.5 py-2 text-[10.5px] text-white/60 leading-relaxed">
|
|
67
|
+
The file is gone. Approving or dismissing accepts that it stays gone — nothing is recreated and the saved
|
|
68
|
+
earlier version stays in history. Revert restores that version.
|
|
69
|
+
</div>;
|
|
70
|
+
}
|
|
65
71
|
if (!hunks) {
|
|
66
72
|
return <div className="px-2.5 py-2 text-[10.5px] text-white/50">
|
|
67
73
|
Diff {preview.diffState}: current {preview.current.state}, baseline {preview.baseline.state} ({preview.baselineProvenance}).
|
|
@@ -74,8 +80,12 @@ function PreviewBody({ rec, preview }: { rec: PendingChangeRecord; preview: Pend
|
|
|
74
80
|
|
|
75
81
|
const BUTTON = 'h-7 px-2.5 rounded-md text-[11px] font-medium transition disabled:opacity-40 inline-flex items-center gap-1.5';
|
|
76
82
|
|
|
77
|
-
export function PendingReviewDialog({ request, agentId, projectId, run, onClose, onDone, next = 0, onNext }: {
|
|
83
|
+
export function PendingReviewDialog({ request, agentId, projectId, run, onClose, onDone, next = 0, onNext, refetch, onBatchStart }: {
|
|
78
84
|
request: ReviewRequest; agentId: string; projectId: string | null; run: VisibleDiffQueue['run'];
|
|
85
|
+
/** The post-409 refetch, when the host owns it (the panel's card then reuses this ONE answer). */
|
|
86
|
+
refetch?: (rec: PendingChangeRecord) => Promise<PendingPreview>;
|
|
87
|
+
/** A batch starts writing: the host holds its revision-driven re-reads until `onDone`. */
|
|
88
|
+
onBatchStart?: () => void;
|
|
79
89
|
onClose: () => void; onDone: (results: ReviewResult[]) => void;
|
|
80
90
|
/** Records still waiting outside this batch; `onNext` (given this batch's ids) opens the next one. */
|
|
81
91
|
next?: number; onNext?: (batch: string[]) => void;
|
|
@@ -85,19 +95,26 @@ export function PendingReviewDialog({ request, agentId, projectId, run, onClose,
|
|
|
85
95
|
() => request.kind === 'single' && request.preview ? { [request.rec.id]: request.preview } : {});
|
|
86
96
|
const [busy, setBusy] = useState(false);
|
|
87
97
|
const [results, setResults] = useState<Record<string, ReviewResult>>({});
|
|
98
|
+
// Liveness is cleared on UNMOUNT only; a per-record sequence drops a superseded answer.
|
|
99
|
+
const mounted = useRef(true);
|
|
100
|
+
const loads = useRef(new Map<string, number>());
|
|
101
|
+
useEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []);
|
|
102
|
+
|
|
103
|
+
const loadPreview = useCallback((rec: PendingChangeRecord, again = false) => {
|
|
104
|
+
const seq = (loads.current.get(rec.id) ?? 0) + 1;
|
|
105
|
+
loads.current.set(rec.id, seq);
|
|
106
|
+
const current = () => mounted.current && loads.current.get(rec.id) === seq;
|
|
107
|
+
const load = () => previewPendingChange({ agentId, ...(projectId ? { projectId } : {}), id: rec.id });
|
|
108
|
+
void (again && refetch ? refetch(rec) : run(load, current))
|
|
109
|
+
.then(preview => { if (current() && preview) setPreviews(p => ({ ...p, [rec.id]: preview })); },
|
|
110
|
+
error => { if (current()) setPreviews(p => ({ ...p, [rec.id]: error instanceof Error ? error : new Error('Preview failed') })); });
|
|
111
|
+
}, [agentId, projectId, run, refetch]);
|
|
88
112
|
|
|
89
113
|
useEffect(() => {
|
|
90
|
-
|
|
91
|
-
for (const rec of records) {
|
|
92
|
-
if (previews[rec.id]) continue;
|
|
93
|
-
void run(() => previewPendingChange({ agentId, ...(projectId ? { projectId } : {}), id: rec.id }), () => live)
|
|
94
|
-
.then(preview => { if (live && preview) setPreviews(p => ({ ...p, [rec.id]: preview })); },
|
|
95
|
-
error => { if (live) setPreviews(p => ({ ...p, [rec.id]: error instanceof Error ? error : new Error('Preview failed') })); });
|
|
96
|
-
}
|
|
97
|
-
return () => { live = false; };
|
|
114
|
+
for (const rec of records) if (!previews[rec.id] && !loads.current.has(rec.id)) loadPreview(rec);
|
|
98
115
|
// Previews are fetched once per opened request; `previews` is read, not tracked.
|
|
99
116
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
100
|
-
}, [records,
|
|
117
|
+
}, [records, loadPreview]);
|
|
101
118
|
|
|
102
119
|
const groups = useMemo(() => {
|
|
103
120
|
const out = new Map<Reason, PendingChangeRecord[]>();
|
|
@@ -110,13 +127,15 @@ export function PendingReviewDialog({ request, agentId, projectId, run, onClose,
|
|
|
110
127
|
return out;
|
|
111
128
|
}, [records, previews]);
|
|
112
129
|
const pending = records.filter(rec => !previews[rec.id]).length;
|
|
113
|
-
// A batch is finished once every record has
|
|
114
|
-
//
|
|
130
|
+
// A batch is finished once every record has a final outcome (or a failed preview);
|
|
131
|
+
// `changed` is not final — its fresh preview waits for a new click. Its group
|
|
132
|
+
// buttons stop accepting clicks and the footer offers the next batch.
|
|
133
|
+
const settled = (rec: PendingChangeRecord) => results[rec.id] !== undefined && results[rec.id].outcome !== 'changed';
|
|
115
134
|
const finished = request.kind === 'group' && pending === 0 && !busy
|
|
116
|
-
&& records.every(rec =>
|
|
135
|
+
&& records.every(rec => settled(rec) || previews[rec.id] instanceof Error);
|
|
117
136
|
|
|
118
137
|
const execute = useCallback(async (targets: PendingChangeRecord[], action: ReviewAction) => {
|
|
119
|
-
setBusy(true);
|
|
138
|
+
setBusy(true); onBatchStart?.();
|
|
120
139
|
const done: ReviewResult[] = [];
|
|
121
140
|
for (const rec of targets) {
|
|
122
141
|
const preview = previews[rec.id];
|
|
@@ -124,10 +143,16 @@ export function PendingReviewDialog({ request, agentId, projectId, run, onClose,
|
|
|
124
143
|
const result = await applyReview({ agentId, projectId }, rec, preview, action, preview.requiresAcknowledgement.length > 0);
|
|
125
144
|
done.push(result);
|
|
126
145
|
setResults(r => ({ ...r, [rec.id]: result }));
|
|
146
|
+
// The state moved under the preview: fetch ONE fresh preview for this record
|
|
147
|
+
// and wait for a new click — a refused action is never replayed by itself.
|
|
148
|
+
if (result.outcome === 'changed') {
|
|
149
|
+
setPreviews(p => { const { [rec.id]: _stale, ...rest } = p; return rest; });
|
|
150
|
+
loadPreview(rec, true);
|
|
151
|
+
}
|
|
127
152
|
}
|
|
128
153
|
setBusy(false);
|
|
129
154
|
onDone(done);
|
|
130
|
-
}, [previews, results, agentId, projectId, onDone]);
|
|
155
|
+
}, [previews, results, agentId, projectId, onDone, loadPreview, onBatchStart]);
|
|
131
156
|
|
|
132
157
|
if (typeof document === 'undefined') return null;
|
|
133
158
|
const single = request.kind === 'single' ? request : null;
|
|
@@ -160,11 +185,11 @@ export function PendingReviewDialog({ request, agentId, projectId, run, onClose,
|
|
|
160
185
|
{REASONS[reason].label} · {recs.length}
|
|
161
186
|
</span>
|
|
162
187
|
{!single ? <div className="ml-auto flex items-center gap-1.5">
|
|
163
|
-
<button type="button" disabled={busy || recs.every(
|
|
188
|
+
<button type="button" disabled={busy || recs.every(settled)} onClick={() => void execute(recs, 'dismiss')}
|
|
164
189
|
className={`${BUTTON} bg-white/[0.04] hover:bg-white/[0.08] text-white/70`}>
|
|
165
190
|
<XCircle className="h-3 w-3" /> Confirm & dismiss {recs.length}
|
|
166
191
|
</button>
|
|
167
|
-
<button type="button" disabled={busy || recs.every(
|
|
192
|
+
<button type="button" disabled={busy || recs.every(settled)} onClick={() => void execute(recs, 'approve')}
|
|
168
193
|
className={`${BUTTON} bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-400/40 text-emerald-100 font-semibold`}>
|
|
169
194
|
<CheckCheck className="h-3 w-3" /> Confirm & approve {recs.length}
|
|
170
195
|
</button>
|
|
@@ -14,9 +14,9 @@ View components for the Files widget: tree, editor, sources panel, packages pane
|
|
|
14
14
|
Re-exported from `app/filesystem/ui/index.ts`:
|
|
15
15
|
|
|
16
16
|
- `FilesystemWidget` — top-level widget; takes `{ agentId, widgetId, state? }`.
|
|
17
|
-
- `FileTree`, `FileTreeNode`, `FileTreeSearch`, `SourceBranchHeader` — virtualised tree components (uses `@tanstack/react-virtual`). `FileTreeSearch` is the search PANEL body only (mode selector `keyword|semantic|grep`, filter row, results) — the query input rides inline in the sidebar HEADER (`FilesystemWidget`), panel visibility keys on `sidebarView === 'search'` alone (the header toggle/Escape clears + closes even with text), and the ≥2-char gate inside the panel only switches results-vs-hint.
|
|
17
|
+
- `FileTree`, `FileTreeNode`, `FileTreeSearch`, `SourceBranchHeader` — virtualised tree components (uses `@tanstack/react-virtual`). `FileTreeSearch` is the search PANEL body only (mode selector `keyword|semantic|grep`, filter row, results) — the query input rides inline in the sidebar HEADER (`FilesystemWidget`), panel visibility keys on `sidebarView === 'search'` alone (the header toggle/Escape clears + closes even with text), and the ≥2-char gate inside the panel only switches results-vs-hint. The store keeps the answer's `requested_mode`/`effective_mode`/`embedding_degraded`/`warning` as `searchEffect` (a `contextResetPatch` key) and the panel prints ONE `searchEffectLine` ("Searched as keyword — semantic search unavailable (<reason>)") plus an `embedding missing|stale` badge per row.
|
|
18
18
|
- `FileTreeContextMenu`, `TreeDeleteConfirm` — the VS Code-style file-management surface (see the section below).
|
|
19
|
-
- `ReembedDialog` — the ONE
|
|
19
|
+
- `ReembedDialog` — the ONE estimate-first embed dialog, shared by the Sources panel (a whole source) and the tree's context menu (a folder or a file): opening it asks `sources/reindex` for a sample-based ESTIMATE only (no queue job, no sync-marker change); the work starts on its button and nowhere else. `kind="reembed"` (default) = the forced re-embed of every file with the active model; `kind="pending"` = **Embed pending…**, the `embeddingOnly` debt pass (only missing/stale embeddings; the estimate is the scope's upper bound). Portaled like `TreeDeleteConfirm`.
|
|
20
20
|
- `DiscoverDock` — the **Discover** list of attachable-but-unattached sources (`GET /sources/discoverable`), pinned below the tree. **It is a SIBLING of the virtualized scroller, never a `FlatItemKind`**: a sixth item kind would have to join the `rowEstimate`/`isMeasuredRow` contract for a list that is never long enough to need virtualizing. Two placements, one component — COLLAPSED it sits in normal flow below the scroller (takes height, covers nothing, bounded to 5 rows); EXPANDED the tree renders it as an `absolute bottom-0` overlay, the ONLY state in which it may hide tree rows. Never both, so the scroller's height cannot change while the overlay is up. Visible only while EVERY source branch is collapsed (`sourceBranchExpanded`, the React-local BRANCH state — not the store's `expandedFolders`) and only for a caller holding `drive.mount`. **Consequence to know:** `activeFile` is persisted and `FileTree`'s auto-expand opens its source branch, so a returning user with an open tab lands with the dock hidden until they collapse that branch. Clicking a row hands the whole suggestion to the Sources panel through the store (`pendingSourceSuggestion`) and opens it — the prefill body lives in `SourcesPanel.applySuggestion`, ONE body shared with the panel's own Discoverable list.
|
|
21
21
|
- `FileEditor`, `FileTab`, `FileMetaBar`, `CodeEditor`, `ReviewDiffView`, `EditorTabBar`, `DiffModeToggle` — the **VS Code-like editor stack**.
|
|
22
22
|
- **`CodeEditor`** (CodeMirror 6 + an in-repo Shiki bridge under `codeEditor/`) is the ONE editor instance behind BOTH the read view (`readOnly`) and edit — toggling Edit only flips the `readOnly` Compartment, so the `EditorView` never remounts (no flash / scroll loss / re-tokenize). Syntax colors are the exact VS Code `dark-plus` TextMate grammars via Shiki's **JavaScript regex engine** — so **no web worker, no `.wasm`, no host CSP change** (`codeEditor/shikiHighlighter.ts` = the memoized core + `tokenizeLines` for the git-diff path — the core, the engine, both themes and all 50 `@shikijs/langs` grammars are dynamic imports, one split chunk each, so nothing of Shiki is on the module's cold path; the 15 base grammars load WITH the core so a markdown fence in one of them colours at once, the rest on first use; `codeEditor/shikiChunks.ts` = the INCREMENTAL tokenizer — position-anchored, line-aligned chunks closed at 64 lines OR 4 KB of text (whichever first) with TextMate grammar-state continuation, chunk-relative token offsets, the visible chunks tokenized synchronously under a 16 KB per-paint character budget (CodeMirror's `visibleRanges` right after a whole-document replace spans thousands of unmeasured lines — the budget, not the viewport estimate, bounds the sync path) and the prefix + a 4-chunk horizon after the viewport in idle slices (a slice is at least one chunk: measured ~90 ms worst case on dense TS/TSX and on long-line markdown, ~50 ms typical code, ~2–5 ms short-line prose — chunks past the horizon wait until scrolled to, so opening a file costs the viewport, not the document), a keystroke re-tokenizing its own chunk and walking forward only until the grammar state converges, a far scroll coloured provisionally first and corrected by the idle pass, and a whole-document replace (every file switch / reload / revert is one) split back into chunks eagerly so the first paint never tokenizes the whole file; `codeEditor/shikiDecorations.ts` = the per-view `ViewPlugin`→`DecorationSet` bridge that owns one engine per editor, because `@shikijs/codemirror` does not exist). The 120k wall is gone; `MAX_HIGHLIGHT_CHARS` (2 MB) is a memory guard on the document (above it the file renders plain), not a tokenization budget. Shared 12px/18px base theme (`codeEditor/editorTheme.ts`).
|
|
@@ -27,7 +27,7 @@ Re-exported from `app/filesystem/ui/index.ts`:
|
|
|
27
27
|
- `DiffView` (the old hand-rolled Myers review diff) was **DELETED** — superseded by `ReviewDiffView`. NB `app/cards/DiffView.tsx` is a DIFFERENT file (the chat-card single-column diff) and stays.
|
|
28
28
|
- `Toolbar` — top toolbar with the active agent’s standalone configured icon (no visible name or identity badge), sidebar toggle, refresh, upload, and panel switches. The Changes switch carries the caller's visible pending COUNT as a static badge (`pendingCount`, the server `count_only` answer — `1000+` at the listing cap); the client `pendingReviewPaths` set only tones tree rows.
|
|
29
29
|
- `SourcesPanel`, `PackagesPanel`, `VectorHealthPanel`, `PanelScaffold` — right-hand panels. Sources offers advisory metrics exclusions only when enabling an attested own data source with no sync block; Save persists the editable draft. Media summaries include local structural PNG failures, and chunk-only cleanup refreshes Files separately from removed-file counts.
|
|
30
|
-
- `PendingChangesPanel` — Changes groups current Git status and pending review by source/repository, including non-Git sources. Normalized URI joining renders a file once while retaining separately labeled staged/unstaged comparisons for each overlapping repository and one pending review. All cards start expanded in ONE `useVirtualizer` list (`pendingChangesModel.ts` owns the filters, the flat row list, `rowEstimate`/`isMeasuredRow` and the Approve split; the compact row pins `h-8`, every card is measured; `overflow-anchor:none` plus an above-the-fold size adjustment keep the view still); the content lanes cache every loaded preview/diff, and source revisions plus context sequences fence obsolete replies. A **Type** filter (all · pending · Git) narrows the layers; the repository picker uses the Git panel's `resolveRepoLabel`. **Approve N** counts records with no acknowledgement on their own fields (`ackHintFor`, the server preview's own predicate); the rest open `PendingReviewDialog` grouped by reason, each record sent with its own fingerprint, and every sweep reports a per-record result row (a refused revert included). The orphan probe runs on mount, Refresh and Repair only — a source revision re-reads the plain list. Source inventory is separate from content, completed siblings are cached, truncation is shown, and Git errors do not hide other sources. Review reads a bounded pending preview/fingerprint with typed current/baseline/index states; ack-required state opens an explicit confirmation. Actual WRITE access gates card, compact, bulk and repair controls; historical cards remain read-only. Orphan ids arrive with the same pending listing, so Repair remains orphan-only and re-authorized server-side.
|
|
30
|
+
- `PendingChangesPanel` — Changes groups current Git status and pending review by source/repository, including non-Git sources. Normalized URI joining renders a file once while retaining separately labeled staged/unstaged comparisons for each overlapping repository and one pending review. All cards start expanded in ONE `useVirtualizer` list (`pendingChangesModel.ts` owns the filters, the flat row list, `rowEstimate`/`isMeasuredRow` and the Approve split; the compact row pins `h-8`, every card is measured; `overflow-anchor:none` plus an above-the-fold size adjustment keep the view still); the content lanes cache every loaded preview/diff, and source revisions plus context sequences fence obsolete replies. A **Type** filter (all · pending · Git) narrows the layers; the repository picker uses the Git panel's `resolveRepoLabel`. **Approve N** counts records with no acknowledgement on their own fields (`ackHintFor`, the server preview's own predicate) or in an already-loaded preview (`splitApprovable`'s `knownPreview`, no fetch of its own); the rest open `PendingReviewDialog` grouped by reason, each record sent with its own fingerprint, and every sweep reports a per-record result row (a refused revert included) — a record the sweep found needing an acknowledgement is reported AND handed to that grouped dialog (approve or dismiss with the acknowledgement). A batch (the sweep, a dialog group) HOLDS the source revisions its own writes bump (`holdRevisions`, released in `afterMutation`): N writes cost N verb requests, ONE re-list and at most one re-preview per card still listed — never a re-list plus every visible card's re-preview per write. A `changed` answer (`PENDING_STATE_CHANGED` / `PENDING_GENERATION_CHANGED`) is not final: the dialog fetches ONE fresh preview for that record (per-record sequence, liveness cleared on unmount only) and waits for a new click — never a replay. In the panel that ONE fetch is the panel's `refetch`: it waits for the re-list, the record's card holds meanwhile and adopts the answer under its final key (`takeRefetch`), so the card sends nothing of its own; a direct (dialog-less) refusal bumps only that card's preview nonce. A confirmed absence with a saved baseline is named in the preview body (`acceptsAbsence`). The orphan probe runs on mount, Refresh and Repair only — a source revision re-reads the plain list. Source inventory is separate from content, completed siblings are cached, truncation is shown, and Git errors do not hide other sources. Review reads a bounded pending preview/fingerprint with typed current/baseline/index states; ack-required state opens an explicit confirmation. Actual WRITE access gates card, compact, bulk and repair controls; historical cards remain read-only. Orphan ids arrive with the same pending listing, so Repair remains orphan-only and re-authorized server-side.
|
|
31
31
|
|
|
32
32
|
- `InlineUploader` — the Upload tab (folder pick, new folder, drop zone). The two modals it once sat beside (`EditFileModal`, `UploadFileModal`) were deleted 2026-09-04: nothing had mounted either since the repository's baseline, so the settling-sandpiper M9 reconcile tails had been fixed on dead code while the live in-place Edit still called the upsert-only `refresh`. Those tails are store actions now (`saveFileInPlace` / `markFileDeleted`, `../store/README.md`) that `FileEditor` calls.
|
|
33
33
|
- `GitControlPanel`, `StackedSections` (+ pure `stackedSectionsLayout.ts`), `GitCommitGraph` (+ pure `gitGraphLayout.ts`/`gitGraphPaging.ts`/`gitGraphItems.ts` — the item model: expandable rows, the outgoing divider, ref-chip/dot classing, the branch-bar ahead/behind chips), `GitDiffView` + `GitFileDiff` + `GitCommitChangesView` (re-exported; + pure `gitDiffExpand.ts`/`gitDiffPatch.ts`/`gitFileDiffMode.ts`), `GitStatusBadge`, `DiffCountBadge` — the Git Control surface: the sidebar pane-stack panel, the in-panel virtualized commit graph with commit drill-in + outgoing markers, the side-by-side diff view (single + multi-file stacked tab), the per-file tree badge. The owning narrative (sections, paging/live-refresh, drill-in, outgoing, caps, write gating) is the package-root README "Git Control panel" section — this bullet only anchors the files.
|
|
@@ -38,19 +38,20 @@ Re-exported from `app/filesystem/ui/index.ts`:
|
|
|
38
38
|
- `SourceTypeIcon` — icon resolver per source kind.
|
|
39
39
|
- `PolicyEditor` — compact admin-style permissions editor for the source-config panel. Renders the persisted JSON tree (`default`, `byRole`, `byUser`, `byAgent`, `paths[]`) with tri-state R/W/X pill badges (allow / deny / inherit cycle), per-role/user/agent override rows (with `+ Add` input + trash), and path rules with clickable role chips + per-rule agent chips (`$self/agentId`, inline-add). `OVR` toggles the rule's `override` flag. Adding a rule **prepends** it (visible at the top of long policy sets; first-match-wins means the fresh rule also takes precedence). Two modes: `edit` (Save button visible when dirty) and `view` (read-only chat-config render). It is wired into `SourcesPanel`, laid out admin-style, and carries the By Role / By User / By Agent sections. Save threads through `handlePolicySave` → `updateSourceConfig` without disturbing the user's pending sync/credential edits.
|
|
40
40
|
- `SourcesPanel` "Discoverable" section — lives INSIDE the Vector Health expandable, alongside a `mount.mts` explainer card. Suggestions arrive from `GET /sources/discoverable` and use the shared `dimmedSourceTone('unattached')` util. The icon name (`AppWindow`, `GitBranch`, `FolderTree`, …) resolves through the shared `resolveLucideIcon` helper (same `ICON_MAP` as the workspace dock-left). Click pre-fills the add-source form with `{ key, path, kind, hostRoot? }` from the suggestion, plus the suggestion's optional `suggestedAccess` (read/write/exec) which overrides the connector-kind default, and — for `seed:'discoverable'` package declarations (e.g. the admin app zone, suggestion id `discover:decl:app`) — the **declared `description`** prefills the add form's description field. The **Full Host** suggestion (native/non-Docker only — `/` is the readable host root; in Docker the host must first be bound via `pnpm neuralis:mount add /`) ships `suggestedAccess: { read:true, write:false, exec:false }` so the whole-host root attaches **read-only by default** — the owner widens it (or narrows the uri-policy) in the form before saving. A host source is readable from the container shell, but host-native binaries still won't execute under the container runtime (a host PTY broker is a separate, future capability).
|
|
41
|
-
- `SourcesPanel` row actions say what they do: **Rescan changes** (the `reindex` route without `force` — changed files are embedded again, unchanged ones keep their vectors), **Re-embed…** (`ReembedDialog` — every file of the source with the active model, estimate first), **Forget sync state** (the `reset` route — the next sync compares every file again; deletes no file and no vector). A started re-embed is followed by the same job poll + count refresh as a rescan (`followReindex`).
|
|
41
|
+
- `SourcesPanel` row actions say what they do: **Rescan changes** (the `reindex` route without `force` — changed files are embedded again, unchanged ones keep their vectors), **Re-embed…** (`ReembedDialog` — every file of the source with the active model, estimate first), **Embed pending…** (`ReembedDialog kind="pending"`; it and **Re-embed…** are disabled while the row's live `embedding_enabled` says off), **Forget sync state** (the `reset` route — the next sync compares every file again; deletes no file and no vector). A started re-embed is followed by the same job poll + count refresh as a rescan (`followReindex`); an embedding pass that ends `paused` (terminal, not failed) shows "Embedding paused: <reason>" with **Resume** (the same owed-only apply). The Modify-source body states `binarySkipLine` ("N binary files not indexed" — the last count a pass measured) and `embeddingLine` beside `mediaSkipLine`: off — the live `embedding_enabled`, never the last pass — reads neutral ("Semantic embedding off — text and field search only"); the last pass's other reasons read amber ("Semantic embedding pending: <reasons>").
|
|
42
42
|
- `SourcesPanel` row is **name-first** (the redesign for the always-narrow widget). The source name is the dominant element (`flex-1`, larger/bold); the kind icon uses the SAME resolved icon set as the file tree (`resolveIconWithFallback` over the connector-manifest `kind_icon`/`meta.icon`) and is **coloured by `getSourceVisualTone`** — so the icon tone IS the sync/live signal, matching the tree (the old `Healthy/Idle/Error` status text + `statusTone` are gone). The per-row **trust badge** (`first-party`/…) and the **scope badge** (`project`/`user`/`agent` — already a tab filter above) are removed; only `observed`, `orphan`, `unsaved`, and the enabled toggle remain inline. Rows are **grouped by `origin_package`** — the **declaring package id** the server resolves per source (the CONTRACT-BACKED `PackageSourceDeclaration` owner, returned on each `SourceRuntimeSummary` / `s.origin_package`), one group **per package**: each real `@neuralis/*` (or any other) package sorts **alphabetically** and user-added mounts collapse into a single **`Mounted (user-added)`** group that sorts **LAST** (`sourceOriginGroup` / `MOUNTED_GROUP`). The small left-indented group header **replaces** the per-row trust/`first-party` badge. The old client-side heuristic that inferred the owner from the connector kind's npm scope (`sourceOriginLabel`) is **gone** — attribution is now resolved server-side from the declarations (`matchDeclaredSource`, slug + resolved-root for rooted kinds; kind-only for rootless sandbox kinds), so a user mount named `app` that is NOT rooted at the platform app zone is NOT attributed to admin — the grouping is **unspoofable**.
|
|
43
43
|
- `SourcesPanel` source body: when a row is expanded it renders at **full height** (no `max-h` clamp) so every Path Rule / uri-policy is visible and the outer panel scrolls — the closed state keeps the collapse animation. The Sync sub-section shows a per-trigger explainer (Manual = only the Sync button; Auto = file-watch + reconcile; Scheduled = interval) and clarifies the "Always active" pin. Disabling sync (unchecking "Enable sync" + Save) sends `clearSync: true` — an omitted `sync` means "no change" server-side, so without the flag the disable would silently revert.
|
|
44
44
|
- `SourcesPanel` per-source counts are **lazy** — the exact `connector` / `indexed` / `unindexed` stats row is fetched ONLY when its row is opened (`toggleSource` → `fetchSourceCounts` → a list scoped to `<source>://` with `include_counts`, so `computeSourcesRegistry` walks that ONE source), cached in `countsBySource`, and shown behind a "Loading counts…" placeholder. Panel-open itself does a **count-FREE** `refresh()` (no `withCounts`) — that still populates `sourceSummaries` (`s.sources`) for source discovery (observed-only sources, project scope) + per-row connector liveness dim, but fires **zero** project-wide count walks. There is deliberately no eager `refresh({withCounts:true})` on open — that, together with the Vector Health walk below, is what used to make opening one panel slow everything. Counted refreshes are narrowed to the operations that actually MOVE a number: sync, reindex, and a config save that changed the sync FILTER set (`syncFilterChanged` — exclude/include/`maxScanFiles`/a cleared sync block, which `connector.count()` applies, so the on-disk total shifts at once). A permissions save, a delete and an add refresh count-FREE: the indexed count is a project-scoped `countByFilter` with no per-caller policy input, the on-disk count is physical truth, a deleted row is gone and a brand-new row's numbers are one expand away. Every counted path also evicts that source's cached counts (`clearCachedCounts`) so the fresh summary is what renders — the lazy cache used to shadow it indefinitely after an exclude edit. `pollSyncJob` (1.5 s × `MAX_POLLS` 2400 = a sixty-minute ceiling) checks a `mountedRef` each turn and returns the moment the panel unmounts; the ref is cleared on UNMOUNT ONLY, never in a dependency-change cleanup, so a live poll is never dropped while the panel is still there. And EVERY hook in `SourcesPanel` sits above its `if (!open) return null` guard: two `useCallback`s used to live below it, so the component rendered a different number of hooks open than closed — React refuses that on one instance, and only the parent's mount gate carrying the same `workspacePanels.sources` flag kept it from ever firing.
|
|
45
45
|
- `useFilesystemSSE` `sync:completed`: the handler is matched **before** the `if (!uri) return` guard — the event carries `source` (the slug), NOT a file `uri`. It runs the authoritative `reconcileSourceFolders` over that source's VISIBLE folders (a sync can have removed files), skips the pass entirely when every change count on the payload is zero (a missing count is unknown and still re-lists), and falls back to `refreshTree` only when no source is present. That pass rides the ONE per-source reconcile schedule shared with external-change events (a trailing quiet window with a max-wait), so a burst of changes becomes one pass. Owner: `app/filesystem/hooks/README.md`.
|
|
46
46
|
- `lib/iconResolver.ts` (2026-05-07) — single source of truth for lucide icon name → component lookup. Exports `ICON_MAP`, `resolveLucideIcon`, `resolveIconWithFallback`, and the `IconComponent` type. Both PascalCase keys (mirror lucide-react) and lowercase aliases (`agent`, `admin`, `brain`, `terminal`, …). The workspace dock-left re-exports it via `neuralis/src/workspace/shell/dockIcons.ts`; the filetree's `usePackageIconBySlug` hook (in `host/buildFilesystemHostValue.ts`) feeds it package-icon names from `/packages/overview` so `packages://<slug>/` and `data://<slug>/` top-level folders render the package's dock-icon instead of the generic Folder.
|
|
47
47
|
- `VectorHealthPanel` per-source table — joins `getSourcesHealth()` with `checkVectorHealth()` to surface a real "Last sync" column (relative age + sync-status colour) per source, instead of being a counts-only view.
|
|
48
|
+
- `VectorHealthPanel` heads its stats with ONE `semanticLine` from the report's `embedding` block (`Semantic: off / metadata only / dense (N missing · M stale)`).
|
|
48
49
|
- `VectorHealthPanel` = **janitor** — the panel's role is "remove leftover records" (deleted past retention, orphaned chunks, duplicate report). Full per-source vector wipe lives on `SourceDeleteConfirm`, not here. `dryRun` default is `false` (clicking actually deletes); destructive buttons need a 2-click confirm; the Deleted stat card shows a `before → after (−Δ)` line after `GC`. Per-action explainer copy + tooltips spell out what each button does (GC / Repair are destructive; Deduplicate is read-only on the service side).
|
|
49
|
-
- `VectorHealthPanel` is **lazy** — `SourcesPanel` renders it ONLY when the "Vector Health" section is expanded (`{healthOpen && …}`), never a CSS-only collapse, because the panel auto-fetches on mount; a mounted-but-hidden panel would fire the health request every time the Sources tab opens. On open it fetches the CHEAP count stats (`checkVectorHealth` → `/health`, count-only). The **Orphans** card and **Duplicate groups** count come from the EXPENSIVE on-demand scan (`scanVectorHealth` → `/health/scan`) behind an explicit **"Scan for problems"** button — they show `—` until the user runs it (and re-scan after a destructive action only if a scan was already loaded). This is the "ne lassítson 1 dolog mindent" rule: opening Sources triggers no collection walk.
|
|
50
|
+
- `VectorHealthPanel` is **lazy** — `SourcesPanel` renders it ONLY when the "Vector Health" section is expanded (`{healthOpen && …}`, the store's `vectorHealthOpen`, so a notification's `open-vector-health` nav can open it; a `contextResetPatch` key), never a CSS-only collapse, because the panel auto-fetches on mount; a mounted-but-hidden panel would fire the health request every time the Sources tab opens. On open it fetches the CHEAP count stats (`checkVectorHealth` → `/health`, count-only). The **Orphans** card and **Duplicate groups** count come from the EXPENSIVE on-demand scan (`scanVectorHealth` → `/health/scan`) behind an explicit **"Scan for problems"** button — they show `—` until the user runs it (and re-scan after a destructive action only if a scan was already loaded). This is the "ne lassítson 1 dolog mindent" rule: opening Sources triggers no collection walk.
|
|
50
51
|
- `SourcesPanel` add form, the ROOT field — **derived from the declaration, not from `isDocker` and not from the kind name** (`lib/sourceKindForm.ts` `rootFieldMeta`, drift-tested against the real manifests in `test/ui/sourceKindForm.test.ts`). Every other field in that form already rendered from its declared `x-neuralis.ui`; `root` was the one hand-written, kind-BLIND exception, which is why the **Host Machine** kind used to show the `local` connector's `/mounts` volume picker, its `pnpm neuralis:mount add` callout and a `/mounts/...` placeholder while its own declaration said `Host root path` / `/home/you/projects` (warded-wren, closing the UI half of F-B5). The discriminator is the declared `x-neuralis.ui.control` on `root`: `container-path` gets the volume picker + callout + Browse; `host-path` gets none of them; a bare legacy `path` (or an absent control) is treated as container-plane, so an externally authored connector is unchanged. **Not `isHostPlaneKind`** — that is a NAME test whose own docstring says a future `ssh`/`smb` kind joins the plane by being added to its body, so a negative test on it would hand the container chrome to every future non-`local` kind. Browse is additionally gated on `canMount` (the shared `meetsRequires` mirror `FileTree` uses) and stays absent for host-plane roots at all times: `GET /sources/browse` readdirs in the SERVER process, the wrong machine for a broker-resolved host root — a host-plane root browser would need a broker-backed listing route, which does not exist.
|
|
51
52
|
- `SourcesPanel` description field — the VALUE is empty whenever the description is RESOLVED rather than stored (`description_effective`), and that is load-bearing: prefilling the value would freeze a copy of the package's wording into this source's config and stop it tracking the package. The resolved text therefore rides as the **placeholder** in edit state (with a one-line note saying an empty box keeps following the package) and as **labelled** text — `"… (from the package)"` — in the read state and the collapsed summary. **Both states must carry the label**: live-testing warded-wren caught it present in the read state and absent in the edit state, where the box simply looked empty; an opacity difference alone is not a label, because it does not survive the state that matters (editing).
|
|
52
53
|
|
|
53
|
-
- `SourceDeleteConfirm` — inline rose-tinted popover under the row (no modal, matches the panel's "no overlay" pattern). Headline + one-line reminder that the host folder is not touched + `[ ] Purge vector index entries (~N point(s))` checkbox (default OFF — conservative) + Docker-bind-mount warning ribbon when `connection.config.hostRoot` is set, telling the user to run `
|
|
54
|
+
- `SourceDeleteConfirm` — inline rose-tinted popover under the row (no modal, matches the panel's "no overlay" pattern). Headline + one-line reminder that the host folder is not touched + `[ ] Purge vector index entries (~N point(s))` checkbox (default OFF — conservative) + Docker-bind-mount warning ribbon when `connection.config.hostRoot` is set, telling the user to run `neuralis mount remove <slug>` before restarting the container. On confirm, `SourcesPanel.handleDelete` calls `deleteSource({ purgeVectors })` and surfaces a transient `successMsg` ("Deleted "<source>" (vectors purged: N)").
|
|
54
55
|
- **`SkillLauncher` coverage** (`@neuralis/package-system/client`, additive "+" launchers) — every filesystem function the LLM can drive has a visible composer-prefill trace. Clicking a launcher PREFILLS the chat composer with the skill (+ scripts) like a `/` slash pick; it never sends/streams. **No launcher states a feature list**: visibility is DERIVED from the host's per-caller skill catalog, so it cannot drift from each skill's own `SKILL.md` `requiredFeatures` and an unresolved catalog renders nothing (the gate named per row below is the SKILL's, documented here, not repeated in the JSX). `packageId` is the REGISTRY id (`@neuralis/brain-core`), matched as an exact pair with the skill id. The launchers resolve the `WorkspaceHostPort` from the `WorkspaceHostPortProvider` mounted once in `host/installFilesystemHostComponents.tsx` — without it they render `null` silently. Current placements:
|
|
55
56
|
- `Toolbar` — `sync` (`drive.sync`, icon-`brain`, script-less) next to the Sources panel toggle, and `manage-files` (`drive.write`; `scripts: mkdir, copy, modify, search`) as the general file-ops handoff. The tree's CONTEXT MENU deliberately carries none: it is a keyboard-navigable click-to-act list with one uniform item model, and a composer-prefill affordance would need a bespoke item kind there.
|
|
56
57
|
- `SourcesPanel` header — `manage-sources` (`drive.read`, attached-left to the `+ Source` button; `scripts: sync, reindex, set-enabled`).
|
|
@@ -69,7 +70,7 @@ Re-exported from `app/filesystem/ui/index.ts`:
|
|
|
69
70
|
|
|
70
71
|
### `recognizesPackages` source toggle + package access-feature governance
|
|
71
72
|
|
|
72
|
-
- **`SourcesPanel`** renders a **Recognize packages** checkbox on BOTH the Add-source wizard and the Modify-source body — shown only for `local`-kind sources AND only to an owner/admin-effective caller (`host.currentRole.priority <= 2`); it is hidden otherwise and the server re-gates. On create it rides the `createSource` body; on edit it joins the shared draft + Save (and is **stripped** from the PATCH of any caller below that strength, so an unrelated description/sync edit never trips the server's strength gate). Turning it on makes the source a runtime
|
|
73
|
+
- **`SourcesPanel`** renders a **Recognize packages** checkbox on BOTH the Add-source wizard and the Modify-source body — shown only for `local`-kind sources AND only to an owner/admin-effective caller (`host.currentRole.priority <= 2`); it is hidden otherwise and the server re-gates. On create it rides the `createSource` body; on edit it joins the shared draft + Save (and is **stripped** from the PATCH of any caller below that strength, so an unrelated description/sync edit never trips the server's strength gate). Turning it on makes the source a runtime package source (additive to `_packages/`); the loaded packages are advertised only within the source's own scope.
|
|
73
74
|
- **`PackagesPanel`** is the consumption-governance surface. Per package (first-party AND project WASM — both appear in the snapshot it lists), an owner/admin-effective caller sees a **base-access feature** control: attach or clear an override feature id (`ProjectRecord.packageAccessFeature`, restrict-only) that hides the whole package from anyone lacking it. The control is a **datalist picker** sourced from the project feature catalog (`getGrantableFeatureIds` → `/api/admin/feature-catalog`; free-text fallback if the fetch fails) — datalist, not a native `<select>`, to avoid the option-popup opacity gotcha; the route rejects an unknown feature id (400). A manifest-declared `requires.accessFeature` shows read-only (cannot be cleared here; an override adds an additional requirement). Non-owners see the effective feature read-only. (Grant the chosen feature to a role in Admin → Roles — a declared/overridden access feature is grantable there.)
|
|
74
75
|
|
|
75
76
|
## Security & scope
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { useEffect, useState } from 'react';
|
|
18
18
|
import { createPortal } from 'react-dom';
|
|
19
19
|
import { Loader2, RefreshCcw, X } from 'lucide-react';
|
|
20
|
-
import { estimateReembed, startReembed, type ReembedEstimate } from '../api';
|
|
20
|
+
import { estimateReembed, startReembed, type ReembedEstimate, type ReembedKind } from '../api';
|
|
21
21
|
|
|
22
22
|
export type ReembedTarget = {
|
|
23
23
|
source: string;
|
|
@@ -47,10 +47,13 @@ export function ReembedDialog({
|
|
|
47
47
|
target,
|
|
48
48
|
onClose,
|
|
49
49
|
onStarted,
|
|
50
|
+
kind = 'reembed',
|
|
50
51
|
}: {
|
|
51
52
|
agentId: string;
|
|
52
53
|
projectId?: string;
|
|
53
54
|
target: ReembedTarget;
|
|
55
|
+
/** `pending` embeds only what is owed (missing / stale) — the same estimate-first confirm. */
|
|
56
|
+
kind?: ReembedKind;
|
|
54
57
|
onClose: () => void;
|
|
55
58
|
/** Called after a job was queued — the caller follows it however it follows sync jobs. */
|
|
56
59
|
onStarted?: () => void;
|
|
@@ -72,7 +75,7 @@ export function ReembedDialog({
|
|
|
72
75
|
useEffect(() => {
|
|
73
76
|
let live = true;
|
|
74
77
|
setEstimating(true);
|
|
75
|
-
estimateReembed(scope)
|
|
78
|
+
estimateReembed(scope, kind)
|
|
76
79
|
.then((result) => {
|
|
77
80
|
if (live) setEstimate(result);
|
|
78
81
|
})
|
|
@@ -105,7 +108,7 @@ export function ReembedDialog({
|
|
|
105
108
|
setStarting(true);
|
|
106
109
|
setError(null);
|
|
107
110
|
try {
|
|
108
|
-
const started = await startReembed(scope);
|
|
111
|
+
const started = await startReembed(scope, kind);
|
|
109
112
|
if (!started.job) {
|
|
110
113
|
// Nothing readable in scope — the route queued nothing. Say so.
|
|
111
114
|
setNothingToDo(true);
|
|
@@ -129,17 +132,25 @@ export function ReembedDialog({
|
|
|
129
132
|
>
|
|
130
133
|
<div
|
|
131
134
|
role="dialog"
|
|
132
|
-
aria-label={
|
|
135
|
+
aria-label={`${kind === 'pending' ? 'Embed pending files of' : 'Re-embed'} ${target.label}`}
|
|
133
136
|
className="w-[360px] rounded-xl border border-cyan-400/25 bg-[#15151d] p-4 shadow-[0_30px_80px_-24px_rgba(0,0,0,0.85)]"
|
|
134
137
|
>
|
|
135
138
|
<div className="flex items-start gap-2.5">
|
|
136
139
|
<RefreshCcw className="h-4 w-4 text-cyan-300 shrink-0 mt-0.5" />
|
|
137
140
|
<div className="min-w-0 flex-1">
|
|
138
|
-
<div className="truncate text-[12px] font-semibold text-white/90">
|
|
141
|
+
<div className="truncate text-[12px] font-semibold text-white/90">
|
|
142
|
+
{kind === 'pending' ? `Embed pending files of ${target.label}?` : `Re-embed ${target.label}?`}
|
|
143
|
+
</div>
|
|
139
144
|
<div className="mt-1 text-[10.5px] text-white/55 leading-relaxed">
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
{kind === 'pending' ? (
|
|
146
|
+
<>Only files whose semantic embedding is missing or stale are embedded, with the{' '}
|
|
147
|
+
<span className="text-white/75">active</span> model. The figures below cover the whole scope — an upper
|
|
148
|
+
bound; files already embedded are skipped. Contents and text search are not touched.</>
|
|
149
|
+
) : (
|
|
150
|
+
<>Every file in this scope is read again and embedded with the <span className="text-white/75">active</span>{' '}
|
|
151
|
+
model, whether it changed or not. File contents and history are not touched — only the vectors are
|
|
152
|
+
replaced.</>
|
|
153
|
+
)}
|
|
143
154
|
</div>
|
|
144
155
|
<div className="mt-2 rounded-md bg-white/[0.03] px-2 py-1.5 text-[10.5px] text-white/65 space-y-0.5">
|
|
145
156
|
{estimating ? (
|
|
@@ -192,7 +203,7 @@ export function ReembedDialog({
|
|
|
192
203
|
className="h-7 px-3 rounded-md text-[11px] font-semibold transition disabled:opacity-40 inline-flex items-center gap-1.5 bg-cyan-500/20 hover:bg-cyan-500/30 border border-cyan-400/40 text-cyan-100"
|
|
193
204
|
>
|
|
194
205
|
{starting ? <Loader2 className="h-3 w-3 animate-spin" /> : <RefreshCcw className="h-3 w-3" />}
|
|
195
|
-
Re-embed
|
|
206
|
+
{kind === 'pending' ? 'Embed pending' : 'Re-embed'}
|
|
196
207
|
</button>
|
|
197
208
|
</div>
|
|
198
209
|
</div>
|
|
@@ -75,7 +75,7 @@ export function SourceDeleteConfirm({
|
|
|
75
75
|
if checked). To also drop the underlying compose bind, run the
|
|
76
76
|
infra-only:{' '}
|
|
77
77
|
<code className="font-mono not-italic text-amber-100/90">
|
|
78
|
-
|
|
78
|
+
neuralis mount remove {source}
|
|
79
79
|
</code>
|
|
80
80
|
</div>
|
|
81
81
|
)}
|