@vincenthanxiaodu/pi-web 1.202608.1
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/LICENSE +21 -0
- package/README.md +185 -0
- package/dist/cli.js +1149 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/apple-touch-icon.png +0 -0
- package/dist/client/assets/CodeViewer-D_Q6ljLV.js +4 -0
- package/dist/client/assets/TerminalPanel-CTGixJIz.js +187 -0
- package/dist/client/assets/index-C2phil_t.js +5637 -0
- package/dist/client/assets/vendor-editor-core-CXO8gGab.js +12 -0
- package/dist/client/assets/vendor-editor-languages-CpW4sJsX.js +46 -0
- package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +1 -0
- package/dist/client/assets/vendor-terminal-BrP-ENHg.css +1 -0
- package/dist/client/assets/vendor-terminal-D8k4UKM2.js +35 -0
- package/dist/client/favicon.svg +11 -0
- package/dist/client/index.html +124 -0
- package/dist/client/manifest.webmanifest +24 -0
- package/dist/client/pwa-icon-192.png +0 -0
- package/dist/client/pwa-icon-512.png +0 -0
- package/dist/config.js +505 -0
- package/dist/config.js.map +1 -0
- package/dist/docker/piWebDockerCommandPlan.js +212 -0
- package/dist/docker/piWebDockerCommandPlan.js.map +1 -0
- package/dist/environment.js +4 -0
- package/dist/environment.js.map +1 -0
- package/dist/nativeServices/installedServiceDefinitions.js +602 -0
- package/dist/nativeServices/installedServiceDefinitions.js.map +1 -0
- package/dist/nativeServices/serviceAction.js +174 -0
- package/dist/nativeServices/serviceAction.js.map +1 -0
- package/dist/nativeServices/serviceDoctor.js +783 -0
- package/dist/nativeServices/serviceDoctor.js.map +1 -0
- package/dist/nativeServices/serviceInstall.js +35 -0
- package/dist/nativeServices/serviceInstall.js.map +1 -0
- package/dist/nativeServices/servicePlan.js +358 -0
- package/dist/nativeServices/servicePlan.js.map +1 -0
- package/dist/nativeServices/serviceProbe.js +447 -0
- package/dist/nativeServices/serviceProbe.js.map +1 -0
- package/dist/nativeServices/serviceRendering.js +132 -0
- package/dist/nativeServices/serviceRendering.js.map +1 -0
- package/dist/pi-web-plugins/git/browser/git-contract.js +108 -0
- package/dist/pi-web-plugins/git/browser/git-panel.js +609 -0
- package/dist/pi-web-plugins/git/browser/gitFileList.js +43 -0
- package/dist/pi-web-plugins/git/browser/gitFileShared.js +16 -0
- package/dist/pi-web-plugins/git/browser/gitFileTree.js +81 -0
- package/dist/pi-web-plugins/git/browser/gitFileViewPreference.js +35 -0
- package/dist/pi-web-plugins/git/browser/gitRoute.js +42 -0
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +10 -0
- package/dist/pi-web-plugins/git/browser/unifiedDiff.js +301 -0
- package/dist/pi-web-plugins/git/git-backend.js +439 -0
- package/dist/pi-web-plugins/git/package.json +16 -0
- package/dist/pi-web-plugins/git/server-plugin.js +176 -0
- package/dist/pi-web-plugins/info/infoInternals.js +222 -0
- package/dist/pi-web-plugins/info/package.json +9 -0
- package/dist/pi-web-plugins/info/pi-web-plugin.js +48 -0
- package/dist/pi-web-plugins/relays/markdownDocument.js +81 -0
- package/dist/pi-web-plugins/relays/package.json +9 -0
- package/dist/pi-web-plugins/relays/pi-web-plugin.js +44 -0
- package/dist/pi-web-plugins/relays/relayDiscovery.js +228 -0
- package/dist/pi-web-plugins/relays/relaysPanelElement.js +550 -0
- package/dist/pi-web-plugins/relays/vendor/README.md +24 -0
- package/dist/pi-web-plugins/relays/vendor/marked.esm.js +76 -0
- package/dist/pi-web-plugins/updates/package.json +9 -0
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +185 -0
- package/dist/pi-web-plugins/updates/updatesLogic.js +97 -0
- package/dist/pi-web-plugins/workspace-tasks/config.js +91 -0
- package/dist/pi-web-plugins/workspace-tasks/package.json +9 -0
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +48 -0
- package/dist/pi-web-plugins/workspace-tasks/taskRunner.js +12 -0
- package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +288 -0
- package/dist/pi-web-plugins/workspace-tasks/workspaceTasksClient.js +47 -0
- package/dist/piWebVersionReport.js +278 -0
- package/dist/piWebVersionReport.js.map +1 -0
- package/dist/plugin-api.d.ts +222 -0
- package/dist/pluginRecoveryCli.js +172 -0
- package/dist/pluginRecoveryCli.js.map +1 -0
- package/dist/server/activeAgentProfileProvider.js +25 -0
- package/dist/server/activeAgentProfileProvider.js.map +1 -0
- package/dist/server/activity/workspaceActivityService.js +106 -0
- package/dist/server/activity/workspaceActivityService.js.map +1 -0
- package/dist/server/app.js +218 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/browserMessageProjection.js +54 -0
- package/dist/server/browserMessageProjection.js.map +1 -0
- package/dist/server/configRoutes.js +293 -0
- package/dist/server/configRoutes.js.map +1 -0
- package/dist/server/diagnostics/nodePtyNativeModule.js +36 -0
- package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -0
- package/dist/server/diagnostics/nodePtySpawnHelper.js +135 -0
- package/dist/server/diagnostics/nodePtySpawnHelper.js.map +1 -0
- package/dist/server/git/gitEnv.js +15 -0
- package/dist/server/git/gitEnv.js.map +1 -0
- package/dist/server/goals/goalArchive.js +184 -0
- package/dist/server/goals/goalArchive.js.map +1 -0
- package/dist/server/goals/goalFile.js +200 -0
- package/dist/server/goals/goalFile.js.map +1 -0
- package/dist/server/goals/goalStore.js +65 -0
- package/dist/server/goals/goalStore.js.map +1 -0
- package/dist/server/index.js +7 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/machines/machineClient.js +191 -0
- package/dist/server/machines/machineClient.js.map +1 -0
- package/dist/server/machines/machinePluginProxyRoutes.js +247 -0
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -0
- package/dist/server/machines/machineProxyRoutes.js +318 -0
- package/dist/server/machines/machineProxyRoutes.js.map +1 -0
- package/dist/server/machines/machineRoutes.js +56 -0
- package/dist/server/machines/machineRoutes.js.map +1 -0
- package/dist/server/machines/machineService.js +274 -0
- package/dist/server/machines/machineService.js.map +1 -0
- package/dist/server/machines/machineStore.js +143 -0
- package/dist/server/machines/machineStore.js.map +1 -0
- package/dist/server/piPackageRoutes.js +91 -0
- package/dist/server/piPackageRoutes.js.map +1 -0
- package/dist/server/piPackageService.js +101 -0
- package/dist/server/piPackageService.js.map +1 -0
- package/dist/server/piWebPluginCatalog.js +584 -0
- package/dist/server/piWebPluginCatalog.js.map +1 -0
- package/dist/server/piWebPluginLifecycle.js +162 -0
- package/dist/server/piWebPluginLifecycle.js.map +1 -0
- package/dist/server/piWebPluginService.js +224 -0
- package/dist/server/piWebPluginService.js.map +1 -0
- package/dist/server/piWebReleaseLookupCache.js +37 -0
- package/dist/server/piWebReleaseLookupCache.js.map +1 -0
- package/dist/server/piWebStatus.js +624 -0
- package/dist/server/piWebStatus.js.map +1 -0
- package/dist/server/piWebStatusCache.js +45 -0
- package/dist/server/piWebStatusCache.js.map +1 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js +69 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -0
- package/dist/server/plugins/serverPluginExec.js +184 -0
- package/dist/server/plugins/serverPluginExec.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +480 -0
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -0
- package/dist/server/projectTrustRoutes.js +80 -0
- package/dist/server/projectTrustRoutes.js.map +1 -0
- package/dist/server/projects/directorySuggestions.js +207 -0
- package/dist/server/projects/directorySuggestions.js.map +1 -0
- package/dist/server/projects/projectService.js +33 -0
- package/dist/server/projects/projectService.js.map +1 -0
- package/dist/server/realtime/sessionEventHub.js +93 -0
- package/dist/server/realtime/sessionEventHub.js.map +1 -0
- package/dist/server/requestCancellation.js +32 -0
- package/dist/server/requestCancellation.js.map +1 -0
- package/dist/server/sessiond/agentHttpDispatcher.js +108 -0
- package/dist/server/sessiond/agentHttpDispatcher.js.map +1 -0
- package/dist/server/sessiond/agentProcessEnvironment.js +61 -0
- package/dist/server/sessiond/agentProcessEnvironment.js.map +1 -0
- package/dist/server/sessiond/pluginBackendRoutes.js +64 -0
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js +31 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +61 -0
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionServiceDependencies.js +34 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -0
- package/dist/server/sessiond/sessiondStateOwnership.js +235 -0
- package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js +31 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js +41 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -0
- package/dist/server/sessiond.js +387 -0
- package/dist/server/sessiond.js.map +1 -0
- package/dist/server/sessions/askUserTool.js +102 -0
- package/dist/server/sessions/askUserTool.js.map +1 -0
- package/dist/server/sessions/attachmentService.js +119 -0
- package/dist/server/sessions/attachmentService.js.map +1 -0
- package/dist/server/sessions/authProviderOptions.js +50 -0
- package/dist/server/sessions/authProviderOptions.js.map +1 -0
- package/dist/server/sessions/authRoutes.js +59 -0
- package/dist/server/sessions/authRoutes.js.map +1 -0
- package/dist/server/sessions/authService.js +122 -0
- package/dist/server/sessions/authService.js.map +1 -0
- package/dist/server/sessions/builtinCommands.js +27 -0
- package/dist/server/sessions/builtinCommands.js.map +1 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js +254 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/editPreview.js +196 -0
- package/dist/server/sessions/editPreview.js.map +1 -0
- package/dist/server/sessions/extensionDialogWaiters.js +76 -0
- package/dist/server/sessions/extensionDialogWaiters.js.map +1 -0
- package/dist/server/sessions/globalProviderPolicy.js +167 -0
- package/dist/server/sessions/globalProviderPolicy.js.map +1 -0
- package/dist/server/sessions/interruptedRunStore.js +147 -0
- package/dist/server/sessions/interruptedRunStore.js.map +1 -0
- package/dist/server/sessions/messagePaging.js +43 -0
- package/dist/server/sessions/messagePaging.js.map +1 -0
- package/dist/server/sessions/modelCatalogRefresher.js +193 -0
- package/dist/server/sessions/modelCatalogRefresher.js.map +1 -0
- package/dist/server/sessions/oauthLoginFlowService.js +346 -0
- package/dist/server/sessions/oauthLoginFlowService.js.map +1 -0
- package/dist/server/sessions/pendingAskStore.js +275 -0
- package/dist/server/sessions/pendingAskStore.js.map +1 -0
- package/dist/server/sessions/pendingExtensionDialogStore.js +196 -0
- package/dist/server/sessions/pendingExtensionDialogStore.js.map +1 -0
- package/dist/server/sessions/piSessionManagerGateway.js +246 -0
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +4059 -0
- package/dist/server/sessions/piSessionService.js.map +1 -0
- package/dist/server/sessions/plainTextTheme.js +112 -0
- package/dist/server/sessions/plainTextTheme.js.map +1 -0
- package/dist/server/sessions/sessionArchiveStore.js +273 -0
- package/dist/server/sessions/sessionArchiveStore.js.map +1 -0
- package/dist/server/sessions/sessionArchiveTree.js +35 -0
- package/dist/server/sessions/sessionArchiveTree.js.map +1 -0
- package/dist/server/sessions/sessionCleanup.js +157 -0
- package/dist/server/sessions/sessionCleanup.js.map +1 -0
- package/dist/server/sessions/sessionCommandService.js +336 -0
- package/dist/server/sessions/sessionCommandService.js.map +1 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js +48 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/sessionFileFormat.js +27 -0
- package/dist/server/sessions/sessionFileFormat.js.map +1 -0
- package/dist/server/sessions/sessionFileHeader.js +104 -0
- package/dist/server/sessions/sessionFileHeader.js.map +1 -0
- package/dist/server/sessions/sessionModelScope.js +141 -0
- package/dist/server/sessions/sessionModelScope.js.map +1 -0
- package/dist/server/sessions/sessionNameGenerator.js +90 -0
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -0
- package/dist/server/sessions/sessionNotificationStore.js +484 -0
- package/dist/server/sessions/sessionNotificationStore.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +744 -0
- package/dist/server/sessions/sessionRoutes.js.map +1 -0
- package/dist/server/sessions/sessionRuntimeStore.js +2 -0
- package/dist/server/sessions/sessionRuntimeStore.js.map +1 -0
- package/dist/server/sessions/sessionService.js +2 -0
- package/dist/server/sessions/sessionService.js.map +1 -0
- package/dist/server/sessions/sessionSummaryScanner.js +491 -0
- package/dist/server/sessions/sessionSummaryScanner.js.map +1 -0
- package/dist/server/sessions/sessionTreeProjection.js +250 -0
- package/dist/server/sessions/sessionTreeProjection.js.map +1 -0
- package/dist/server/sessions/sessionUnreadStore.js +520 -0
- package/dist/server/sessions/sessionUnreadStore.js.map +1 -0
- package/dist/server/sessions/shutdownDrain.js +73 -0
- package/dist/server/sessions/shutdownDrain.js.map +1 -0
- package/dist/server/sessions/spawnSessionTool.js +48 -0
- package/dist/server/sessions/spawnSessionTool.js.map +1 -0
- package/dist/server/sessions/spawnSubsessionTool.js +216 -0
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -0
- package/dist/server/sessions/spawnTargetResolver.js +23 -0
- package/dist/server/sessions/spawnTargetResolver.js.map +1 -0
- package/dist/server/sessions/subsessionTranscript.js +235 -0
- package/dist/server/sessions/subsessionTranscript.js.map +1 -0
- package/dist/server/status/machineStatusRoutes.js +8 -0
- package/dist/server/status/machineStatusRoutes.js.map +1 -0
- package/dist/server/status/machineStatusService.js +181 -0
- package/dist/server/status/machineStatusService.js.map +1 -0
- package/dist/server/status/workspaceAttribution.js +87 -0
- package/dist/server/status/workspaceAttribution.js.map +1 -0
- package/dist/server/storage/projectStore.js +88 -0
- package/dist/server/storage/projectStore.js.map +1 -0
- package/dist/server/terminalProxyRoutes.js +141 -0
- package/dist/server/terminalProxyRoutes.js.map +1 -0
- package/dist/server/terminals/terminalRoutes.js +155 -0
- package/dist/server/terminals/terminalRoutes.js.map +1 -0
- package/dist/server/terminals/terminalService.js +305 -0
- package/dist/server/terminals/terminalService.js.map +1 -0
- package/dist/server/terminals/terminalSize.js +17 -0
- package/dist/server/terminals/terminalSize.js.map +1 -0
- package/dist/server/types.js +2 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server/updates/fleetRoutes.js +111 -0
- package/dist/server/updates/fleetRoutes.js.map +1 -0
- package/dist/server/updates/restartRoutes.js +48 -0
- package/dist/server/updates/restartRoutes.js.map +1 -0
- package/dist/server/updates/selfUpdateRoutes.js +145 -0
- package/dist/server/updates/selfUpdateRoutes.js.map +1 -0
- package/dist/server/webSocketBridge.js +32 -0
- package/dist/server/webSocketBridge.js.map +1 -0
- package/dist/server/workingDirectory.js +44 -0
- package/dist/server/workingDirectory.js.map +1 -0
- package/dist/server/workspaceExplorerRoutes.js +147 -0
- package/dist/server/workspaceExplorerRoutes.js.map +1 -0
- package/dist/server/workspaces/effectivePathAccess.js +8 -0
- package/dist/server/workspaces/effectivePathAccess.js.map +1 -0
- package/dist/server/workspaces/fileContentService.js +190 -0
- package/dist/server/workspaces/fileContentService.js.map +1 -0
- package/dist/server/workspaces/filePreviewResponseHeaders.js +18 -0
- package/dist/server/workspaces/filePreviewResponseHeaders.js.map +1 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js +55 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js.map +1 -0
- package/dist/server/workspaces/filePreviewService.js +81 -0
- package/dist/server/workspaces/filePreviewService.js.map +1 -0
- package/dist/server/workspaces/fileSuggestions.js +520 -0
- package/dist/server/workspaces/fileSuggestions.js.map +1 -0
- package/dist/server/workspaces/fileTreeService.js +35 -0
- package/dist/server/workspaces/fileTreeService.js.map +1 -0
- package/dist/server/workspaces/pathAccessPolicy.js +90 -0
- package/dist/server/workspaces/pathAccessPolicy.js.map +1 -0
- package/dist/server/workspaces/pathSafety.js +45 -0
- package/dist/server/workspaces/pathSafety.js.map +1 -0
- package/dist/server/workspaces/projectPiWebConfig.js +61 -0
- package/dist/server/workspaces/projectPiWebConfig.js.map +1 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js +17 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -0
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +402 -0
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceCatalog.js +47 -0
- package/dist/server/workspaces/workspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceContext.js +6 -0
- package/dist/server/workspaces/workspaceContext.js.map +1 -0
- package/dist/server/workspaces/workspaceDeletionRoutes.js +49 -0
- package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js +651 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -0
- package/dist/server/workspaces/workspaceRemovalService.js +256 -0
- package/dist/server/workspaces/workspaceRemovalService.js.map +1 -0
- package/dist/server/workspaces/workspaceRouteErrors.js +7 -0
- package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js +39 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js.map +1 -0
- package/dist/server-plugin-api.d.ts +140 -0
- package/dist/server-plugin-api.js +2 -0
- package/dist/server-plugin-api.js.map +1 -0
- package/dist/serverPluginRecovery.js +138 -0
- package/dist/serverPluginRecovery.js.map +1 -0
- package/dist/sessiond/activeAgentProfile.js +12 -0
- package/dist/sessiond/activeAgentProfile.js.map +1 -0
- package/dist/sessiond/config.js +20 -0
- package/dist/sessiond/config.js.map +1 -0
- package/dist/sessiond/sessionDaemonClient.js +105 -0
- package/dist/sessiond/sessionDaemonClient.js.map +1 -0
- package/dist/shared/activeAgentProfile.js +37 -0
- package/dist/shared/activeAgentProfile.js.map +1 -0
- package/dist/shared/activity.js +16 -0
- package/dist/shared/activity.js.map +1 -0
- package/dist/shared/apiTypes.js +41 -0
- package/dist/shared/apiTypes.js.map +1 -0
- package/dist/shared/capabilities.js +37 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/federatedRoutes.js +127 -0
- package/dist/shared/federatedRoutes.js.map +1 -0
- package/dist/shared/machinePluginIds.js +43 -0
- package/dist/shared/machinePluginIds.js.map +1 -0
- package/dist/shared/machineStatus.js +94 -0
- package/dist/shared/machineStatus.js.map +1 -0
- package/dist/shared/piWebStatusParsing.js +141 -0
- package/dist/shared/piWebStatusParsing.js.map +1 -0
- package/dist/shared/pluginApiTypes.d.ts +150 -0
- package/dist/shared/pluginApiTypes.js +3 -0
- package/dist/shared/pluginApiTypes.js.map +1 -0
- package/dist/shared/pluginBackendProtocol.js +110 -0
- package/dist/shared/pluginBackendProtocol.js.map +1 -0
- package/dist/shared/pluginIds.js +10 -0
- package/dist/shared/pluginIds.js.map +1 -0
- package/dist/shared/pluginRecoveryCommands.js +14 -0
- package/dist/shared/pluginRecoveryCommands.js.map +1 -0
- package/dist/shared/promptAttachments.js +92 -0
- package/dist/shared/promptAttachments.js.map +1 -0
- package/dist/shared/selfUpdate.js +3 -0
- package/dist/shared/selfUpdate.js.map +1 -0
- package/dist/shared/thinkingLevels.js +31 -0
- package/dist/shared/thinkingLevels.js.map +1 -0
- package/dist/shared/workspaceDeletion.js +12 -0
- package/dist/shared/workspaceDeletion.js.map +1 -0
- package/dist/shared/workspaceFiles.js +42 -0
- package/dist/shared/workspaceFiles.js.map +1 -0
- package/dist/shared/workspaceRemovalProtocol.js +24 -0
- package/dist/shared/workspaceRemovalProtocol.js.map +1 -0
- package/docs/assets/favicon.svg +11 -0
- package/docs/assets/pi-web-banner.png +0 -0
- package/docs/assets/pi-web-desktop.png +0 -0
- package/docs/assets/pi-web-mobile.png +0 -0
- package/docs/assets/pi-web-tablet.png +0 -0
- package/docs/config.md +444 -0
- package/docs/plugins.md +1305 -0
- package/examples/workspace-provider-plugin/README.md +52 -0
- package/examples/workspace-provider-plugin/package.json +25 -0
- package/examples/workspace-provider-plugin/src/browser/index.ts +69 -0
- package/examples/workspace-provider-plugin/src/server.ts +64 -0
- package/examples/workspace-provider-plugin/tsconfig.json +21 -0
- package/extensions/README-pi-web-restart-migration.md +45 -0
- package/extensions/pi-web.ts +324 -0
- package/install.sh +5 -0
- package/package.json +173 -0
- package/plugin-api.d.ts +1 -0
- package/server-plugin-api.d.ts +1 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/relays/relaysPanelElement.ts. Do not edit directly.
|
|
2
|
+
import { isMarkdownDocumentPath, renderRelayDocumentHtml } from "./markdownDocument.js";
|
|
3
|
+
import { ancestorDirectoryPaths, collectDirectoryPaths, defaultRelayDocument, flattenRelayTree, listRelayDocumentTree, listWorkspaceRelays, readRelayDocument, RELAYS_ROOT, } from "./relayDiscovery.js";
|
|
4
|
+
export const relaysPanelTagName = "pi-web-relays-panel";
|
|
5
|
+
export function defineRelaysPanelElement() {
|
|
6
|
+
if (!customElements.get(relaysPanelTagName))
|
|
7
|
+
customElements.define(relaysPanelTagName, PiWebRelaysPanel);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Read-only relay browser: relay picker (auto-opens a single relay), one tab
|
|
11
|
+
* per relay document, and a document viewer rendering markdown documents as
|
|
12
|
+
* sanitized HTML and everything else as preformatted text. Documents in
|
|
13
|
+
* subfolders appear as folder chips that expand inline into the tab strip
|
|
14
|
+
* (accordion: expanding one folder collapses its siblings at the same level);
|
|
15
|
+
* an expanded folder wraps its chip and children in a group so nested tabs
|
|
16
|
+
* stay visually contained. Collapsing a folder keeps the selected document
|
|
17
|
+
* open and highlights the folder instead. All async loads flow through
|
|
18
|
+
* scanToken so stale responses for a previous workspace or selection never
|
|
19
|
+
* overwrite newer state.
|
|
20
|
+
*
|
|
21
|
+
* Rendering is region-scoped: the toolbar, tab strip, and viewer are
|
|
22
|
+
* persistent elements built once, and each async stage re-renders only its
|
|
23
|
+
* own region. Clicking a tab never rebuilds the strip — it toggles the
|
|
24
|
+
* active marker in place and re-renders the viewer — so the strip's
|
|
25
|
+
* horizontal scroll position and keyboard focus survive document switches.
|
|
26
|
+
*/
|
|
27
|
+
class PiWebRelaysPanel extends HTMLElement {
|
|
28
|
+
contextValue;
|
|
29
|
+
listing;
|
|
30
|
+
selectedRelayPath;
|
|
31
|
+
documents;
|
|
32
|
+
selectedDocumentPath;
|
|
33
|
+
documentContent;
|
|
34
|
+
/** Directory paths currently expanded in the tab strip; an accordion spine, cleared on relay/workspace switch. */
|
|
35
|
+
expandedDirs = new Set();
|
|
36
|
+
scanToken = 0;
|
|
37
|
+
root;
|
|
38
|
+
toolbar;
|
|
39
|
+
tabStrip;
|
|
40
|
+
viewer;
|
|
41
|
+
constructor() {
|
|
42
|
+
super();
|
|
43
|
+
this.root = this.attachShadow({ mode: "open" });
|
|
44
|
+
this.root.innerHTML = `
|
|
45
|
+
${relaysStyles()}
|
|
46
|
+
<section class="toolbar" hidden></section>
|
|
47
|
+
<nav class="document-tabs" aria-label="Relay documents" hidden></nav>
|
|
48
|
+
<section class="viewer"><div class="empty">Select a workspace.</div></section>
|
|
49
|
+
`;
|
|
50
|
+
this.toolbar = requiredRegion(this.root, ".toolbar");
|
|
51
|
+
this.tabStrip = requiredRegion(this.root, "nav.document-tabs");
|
|
52
|
+
this.viewer = requiredRegion(this.root, ".viewer");
|
|
53
|
+
// Listeners bind once against the persistent regions and delegate to
|
|
54
|
+
// whichever controls the latest region render produced.
|
|
55
|
+
this.toolbar.addEventListener("click", (event) => {
|
|
56
|
+
const button = event.target instanceof Element ? event.target.closest("button[data-refresh]") : null;
|
|
57
|
+
if (button !== null)
|
|
58
|
+
this.refresh();
|
|
59
|
+
});
|
|
60
|
+
this.toolbar.addEventListener("change", (event) => {
|
|
61
|
+
const picker = event.target;
|
|
62
|
+
if (!(picker instanceof HTMLSelectElement) || !picker.matches("select[data-relay-picker]"))
|
|
63
|
+
return;
|
|
64
|
+
const context = this.contextValue;
|
|
65
|
+
if (context !== undefined)
|
|
66
|
+
void this.openRelay(context, picker.value);
|
|
67
|
+
});
|
|
68
|
+
this.tabStrip.addEventListener("click", (event) => {
|
|
69
|
+
const element = event.target instanceof Element ? event.target : null;
|
|
70
|
+
const chip = element?.closest("button[data-directory-path]") ?? null;
|
|
71
|
+
if (chip !== null) {
|
|
72
|
+
const directoryPath = chip.getAttribute("data-directory-path");
|
|
73
|
+
if (directoryPath !== null)
|
|
74
|
+
this.toggleDirectory(directoryPath);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const tab = element?.closest("button[data-document-path]") ?? null;
|
|
78
|
+
if (tab === null)
|
|
79
|
+
return;
|
|
80
|
+
const documentPath = tab.getAttribute("data-document-path");
|
|
81
|
+
const context = this.contextValue;
|
|
82
|
+
if (context !== undefined && documentPath !== null)
|
|
83
|
+
void this.openDocument(context, documentPath);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
set context(value) {
|
|
87
|
+
const previousKey = this.contextValue === undefined ? undefined : contextKey(this.contextValue);
|
|
88
|
+
const nextKey = value === undefined ? undefined : contextKey(value);
|
|
89
|
+
this.contextValue = value;
|
|
90
|
+
// Parent app updates should not rescan or re-render this panel for the
|
|
91
|
+
// same workspace (mirrors the workspace-tasks panel).
|
|
92
|
+
if (previousKey === nextKey)
|
|
93
|
+
return;
|
|
94
|
+
// A different workspace starts with every folder collapsed.
|
|
95
|
+
this.expandedDirs = new Set();
|
|
96
|
+
if (value === undefined) {
|
|
97
|
+
this.resetScanState();
|
|
98
|
+
this.renderAll();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
void this.scan(value, {});
|
|
102
|
+
}
|
|
103
|
+
/** Rescan relays, then reload the selected relay's documents and the open document. */
|
|
104
|
+
async scan(context, selection) {
|
|
105
|
+
const token = ++this.scanToken;
|
|
106
|
+
this.resetScanState();
|
|
107
|
+
this.renderAll();
|
|
108
|
+
const listing = await listWorkspaceRelays(context.files);
|
|
109
|
+
if (!this.isCurrentScan(context, token))
|
|
110
|
+
return;
|
|
111
|
+
this.listing = listing;
|
|
112
|
+
// listWorkspaceRelays returns most recently modified first, so the first
|
|
113
|
+
// relay is the default pre-selection.
|
|
114
|
+
const relay = listing.kind === "loaded"
|
|
115
|
+
? listing.relays.find((candidate) => candidate.path === selection.relayPath) ?? listing.relays[0]
|
|
116
|
+
: undefined;
|
|
117
|
+
this.selectedRelayPath = relay?.path;
|
|
118
|
+
this.renderToolbar();
|
|
119
|
+
if (relay === undefined) {
|
|
120
|
+
this.renderViewer();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
await this.loadDocuments(context, token, relay.path, selection.documentPath);
|
|
124
|
+
}
|
|
125
|
+
async openRelay(context, relayPath) {
|
|
126
|
+
const token = ++this.scanToken;
|
|
127
|
+
this.selectedRelayPath = relayPath;
|
|
128
|
+
this.expandedDirs = new Set();
|
|
129
|
+
await this.loadDocuments(context, token, relayPath, undefined);
|
|
130
|
+
}
|
|
131
|
+
/** Expand or collapse a folder chip; the accordion rule keeps at most one expanded folder per level. */
|
|
132
|
+
toggleDirectory(directoryPath) {
|
|
133
|
+
if (this.documents?.kind !== "loaded")
|
|
134
|
+
return;
|
|
135
|
+
if (this.expandedDirs.has(directoryPath)) {
|
|
136
|
+
for (const path of [...this.expandedDirs]) {
|
|
137
|
+
if (path === directoryPath || path.startsWith(`${directoryPath}/`))
|
|
138
|
+
this.expandedDirs.delete(path);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
this.collapseSiblingDirectories(directoryPath);
|
|
143
|
+
this.expandedDirs.add(directoryPath);
|
|
144
|
+
}
|
|
145
|
+
this.renderTabs();
|
|
146
|
+
const chip = this.findDirectoryChip(directoryPath);
|
|
147
|
+
// The innerHTML rebuild destroyed the clicked button; give focus back so
|
|
148
|
+
// keyboard users stay on the control they just toggled.
|
|
149
|
+
chip?.focus();
|
|
150
|
+
if (this.expandedDirs.has(directoryPath))
|
|
151
|
+
this.scrollExpandedChildrenIntoView(chip);
|
|
152
|
+
}
|
|
153
|
+
/** Expanding a folder collapses its siblings (same parent) and everything expanded under them. */
|
|
154
|
+
collapseSiblingDirectories(directoryPath) {
|
|
155
|
+
const parentPath = directoryPath.slice(0, directoryPath.lastIndexOf("/"));
|
|
156
|
+
for (const expanded of [...this.expandedDirs]) {
|
|
157
|
+
if (expanded.slice(0, expanded.lastIndexOf("/")) !== parentPath)
|
|
158
|
+
continue;
|
|
159
|
+
this.expandedDirs.delete(expanded);
|
|
160
|
+
for (const path of [...this.expandedDirs]) {
|
|
161
|
+
if (path.startsWith(`${expanded}/`))
|
|
162
|
+
this.expandedDirs.delete(path);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Expand every ancestor of a freshly chosen default document so it is visible. */
|
|
167
|
+
revealDocument(documentPath) {
|
|
168
|
+
const documents = this.documents;
|
|
169
|
+
if (documents?.kind !== "loaded")
|
|
170
|
+
return;
|
|
171
|
+
for (const path of ancestorDirectoryPaths(documents.tree, documentPath))
|
|
172
|
+
this.expandedDirs.add(path);
|
|
173
|
+
}
|
|
174
|
+
/** Drop expansion state for directories that vanished since the last scan. */
|
|
175
|
+
pruneExpandedDirs(tree) {
|
|
176
|
+
const existing = collectDirectoryPaths(tree);
|
|
177
|
+
for (const path of [...this.expandedDirs]) {
|
|
178
|
+
if (!existing.has(path))
|
|
179
|
+
this.expandedDirs.delete(path);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
findDirectoryChip(directoryPath) {
|
|
183
|
+
return [...this.tabStrip.querySelectorAll("button[data-directory-path]")]
|
|
184
|
+
.find((candidate) => candidate instanceof HTMLElement && candidate.getAttribute("data-directory-path") === directoryPath);
|
|
185
|
+
}
|
|
186
|
+
scrollExpandedChildrenIntoView(chip) {
|
|
187
|
+
const firstChild = chip?.nextElementSibling;
|
|
188
|
+
if (firstChild instanceof HTMLElement)
|
|
189
|
+
firstChild.scrollIntoView({ inline: "nearest", block: "nearest" });
|
|
190
|
+
}
|
|
191
|
+
async openDocument(context, documentPath) {
|
|
192
|
+
const token = ++this.scanToken;
|
|
193
|
+
this.selectedDocumentPath = documentPath;
|
|
194
|
+
// The tab set is unchanged: toggle the active marker on the mounted
|
|
195
|
+
// buttons instead of rebuilding the strip, so its scroll position and
|
|
196
|
+
// focus stay put. A different document starts reading from the top.
|
|
197
|
+
this.updateActiveTab();
|
|
198
|
+
this.viewer.scrollTop = 0;
|
|
199
|
+
await this.loadDocumentContent(context, token, documentPath);
|
|
200
|
+
}
|
|
201
|
+
async loadDocuments(context, token, relayPath, preferredDocumentPath) {
|
|
202
|
+
this.documents = undefined;
|
|
203
|
+
this.selectedDocumentPath = undefined;
|
|
204
|
+
this.documentContent = undefined;
|
|
205
|
+
this.renderTabs();
|
|
206
|
+
this.renderViewer();
|
|
207
|
+
const documents = await listRelayDocumentTree(context.files, relayPath);
|
|
208
|
+
if (!this.isCurrentScan(context, token))
|
|
209
|
+
return;
|
|
210
|
+
this.documents = documents;
|
|
211
|
+
const files = documents.kind === "loaded" ? flattenRelayTree(documents.tree) : [];
|
|
212
|
+
const preferred = files.find((candidate) => candidate.path === preferredDocumentPath);
|
|
213
|
+
const document = preferred ?? defaultRelayDocument(documents.kind === "loaded" ? documents.tree : []);
|
|
214
|
+
this.selectedDocumentPath = document?.path;
|
|
215
|
+
if (documents.kind === "loaded")
|
|
216
|
+
this.pruneExpandedDirs(documents.tree);
|
|
217
|
+
// A freshly chosen default must be visible; a restored preferred pick keeps
|
|
218
|
+
// whatever the user expanded (a hidden pick is surfaced by the folder highlight).
|
|
219
|
+
if (preferred === undefined && document !== undefined)
|
|
220
|
+
this.revealDocument(document.path);
|
|
221
|
+
this.renderTabs();
|
|
222
|
+
if (document === undefined) {
|
|
223
|
+
this.renderViewer();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
await this.loadDocumentContent(context, token, document.path);
|
|
227
|
+
}
|
|
228
|
+
async loadDocumentContent(context, token, documentPath) {
|
|
229
|
+
this.documentContent = undefined;
|
|
230
|
+
this.renderViewer();
|
|
231
|
+
const content = await readRelayDocument(context.files, documentPath);
|
|
232
|
+
if (!this.isCurrentScan(context, token))
|
|
233
|
+
return;
|
|
234
|
+
this.documentContent = content;
|
|
235
|
+
this.renderViewer();
|
|
236
|
+
}
|
|
237
|
+
refresh() {
|
|
238
|
+
const context = this.contextValue;
|
|
239
|
+
if (context === undefined)
|
|
240
|
+
return;
|
|
241
|
+
void this.scan(context, { relayPath: this.selectedRelayPath, documentPath: this.selectedDocumentPath });
|
|
242
|
+
}
|
|
243
|
+
resetScanState() {
|
|
244
|
+
this.listing = undefined;
|
|
245
|
+
this.selectedRelayPath = undefined;
|
|
246
|
+
this.documents = undefined;
|
|
247
|
+
this.selectedDocumentPath = undefined;
|
|
248
|
+
this.documentContent = undefined;
|
|
249
|
+
}
|
|
250
|
+
isCurrentScan(context, token) {
|
|
251
|
+
return token === this.scanToken && this.contextValue !== undefined && contextKey(this.contextValue) === contextKey(context);
|
|
252
|
+
}
|
|
253
|
+
renderAll() {
|
|
254
|
+
this.renderToolbar();
|
|
255
|
+
this.renderTabs();
|
|
256
|
+
this.renderViewer();
|
|
257
|
+
}
|
|
258
|
+
renderToolbar() {
|
|
259
|
+
if (this.contextValue === undefined) {
|
|
260
|
+
this.toolbar.hidden = true;
|
|
261
|
+
this.toolbar.replaceChildren();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
this.toolbar.hidden = false;
|
|
265
|
+
this.toolbar.innerHTML = `
|
|
266
|
+
<strong>Relays</strong>
|
|
267
|
+
<span class="toolbar-actions">
|
|
268
|
+
${this.renderRelayPicker()}
|
|
269
|
+
<button class="icon-button" data-refresh aria-label="Refresh" title="Refresh">${refreshIconSvg()}</button>
|
|
270
|
+
</span>
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
273
|
+
renderRelayPicker() {
|
|
274
|
+
const listing = this.listing;
|
|
275
|
+
if (listing?.kind !== "loaded" || listing.relays.length === 0)
|
|
276
|
+
return "";
|
|
277
|
+
// A single relay opens immediately; a one-option picker would be noise.
|
|
278
|
+
if (listing.relays.length === 1) {
|
|
279
|
+
const relay = listing.relays[0];
|
|
280
|
+
return relay === undefined ? "" : `<span class="relay-name" title="${escapeAttr(relay.path)}">${escapeHtml(relay.name)}</span>`;
|
|
281
|
+
}
|
|
282
|
+
const options = listing.relays.map((relay) => {
|
|
283
|
+
const selected = relay.path === this.selectedRelayPath ? " selected" : "";
|
|
284
|
+
return `<option value="${escapeAttr(relay.path)}"${selected}>${escapeHtml(relay.name)}</option>`;
|
|
285
|
+
}).join("");
|
|
286
|
+
return `<select data-relay-picker aria-label="Relay">${options}</select>`;
|
|
287
|
+
}
|
|
288
|
+
renderTabs() {
|
|
289
|
+
const documents = this.documents;
|
|
290
|
+
if (documents?.kind !== "loaded" || documents.documentCount === 0) {
|
|
291
|
+
this.tabStrip.hidden = true;
|
|
292
|
+
// A new tab set starts at the left edge, not at the previous set's offset.
|
|
293
|
+
this.tabStrip.replaceChildren();
|
|
294
|
+
this.tabStrip.scrollLeft = 0;
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
this.tabStrip.hidden = false;
|
|
298
|
+
const containsActivePath = this.selectedDocumentPath === undefined
|
|
299
|
+
? undefined
|
|
300
|
+
: collapsedAncestorOfSelectedFile(documents.tree, this.selectedDocumentPath, this.expandedDirs);
|
|
301
|
+
// The strip element itself persists across re-renders, so replacing its
|
|
302
|
+
// buttons keeps the container's horizontal scroll position.
|
|
303
|
+
this.tabStrip.innerHTML = this.renderStripNodes(documents.tree, containsActivePath);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Depth-first strip markup: an expanded folder wraps its chip and children
|
|
307
|
+
* in one group element so nested tabs read as contained by their folder.
|
|
308
|
+
* Collapsed folders (and folders without listed children) render as a bare chip.
|
|
309
|
+
*/
|
|
310
|
+
renderStripNodes(nodes, containsActivePath) {
|
|
311
|
+
return nodes.map((node) => {
|
|
312
|
+
if (node.kind === "file")
|
|
313
|
+
return this.renderFileTab(node);
|
|
314
|
+
const chip = this.renderDirectoryChip(node, containsActivePath);
|
|
315
|
+
if (!this.expandedDirs.has(node.path) || node.children.length === 0)
|
|
316
|
+
return chip;
|
|
317
|
+
return `<span class="directory-group">${chip}${this.renderStripNodes(node.children, containsActivePath)}</span>`;
|
|
318
|
+
}).join("");
|
|
319
|
+
}
|
|
320
|
+
renderFileTab(file) {
|
|
321
|
+
const active = file.path === this.selectedDocumentPath;
|
|
322
|
+
return `<button class="document-tab${active ? " active" : ""}" data-document-path="${escapeAttr(file.path)}" title="${escapeAttr(file.relativePath)}"${active ? ' aria-current="true"' : ""}>${escapeHtml(file.name)}</button>`;
|
|
323
|
+
}
|
|
324
|
+
renderDirectoryChip(directory, containsActivePath) {
|
|
325
|
+
const expanded = this.expandedDirs.has(directory.path);
|
|
326
|
+
const containsActive = directory.path === containsActivePath;
|
|
327
|
+
const title = containsActive ? "Contains the open document" : directory.relativePath;
|
|
328
|
+
return `<button class="document-tab directory-tab${containsActive ? " contains-active" : ""}" data-directory-path="${escapeAttr(directory.path)}" title="${escapeAttr(title)}" aria-expanded="${expanded ? "true" : "false"}">${chevronSvg()}${escapeHtml(directory.name)}</button>`;
|
|
329
|
+
}
|
|
330
|
+
/** Move the active marker between the mounted tab buttons without rebuilding them. */
|
|
331
|
+
updateActiveTab() {
|
|
332
|
+
for (const tab of this.tabStrip.querySelectorAll("button[data-document-path]")) {
|
|
333
|
+
const active = tab.getAttribute("data-document-path") === this.selectedDocumentPath;
|
|
334
|
+
tab.classList.toggle("active", active);
|
|
335
|
+
if (active)
|
|
336
|
+
tab.setAttribute("aria-current", "true");
|
|
337
|
+
else
|
|
338
|
+
tab.removeAttribute("aria-current");
|
|
339
|
+
}
|
|
340
|
+
// The clicked tab is visible by definition, so no folder highlight remains.
|
|
341
|
+
for (const chip of this.tabStrip.querySelectorAll("button[data-directory-path]"))
|
|
342
|
+
chip.classList.remove("contains-active");
|
|
343
|
+
}
|
|
344
|
+
renderViewer() {
|
|
345
|
+
if (this.contextValue === undefined) {
|
|
346
|
+
this.viewer.innerHTML = `<div class="empty">Select a workspace.</div>`;
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
this.viewer.innerHTML = this.renderViewerContent();
|
|
350
|
+
}
|
|
351
|
+
renderViewerContent() {
|
|
352
|
+
const listing = this.listing;
|
|
353
|
+
if (listing === undefined)
|
|
354
|
+
return `<p class="muted">Scanning ${escapeHtml(RELAYS_ROOT)}…</p>`;
|
|
355
|
+
if (listing.kind === "unavailable")
|
|
356
|
+
return renderErrorState("Could not scan workspace relays.", listing.detail);
|
|
357
|
+
if (listing.kind === "missing" || listing.relays.length === 0)
|
|
358
|
+
return renderEmptyState();
|
|
359
|
+
return this.renderSelectedRelay();
|
|
360
|
+
}
|
|
361
|
+
renderSelectedRelay() {
|
|
362
|
+
const documents = this.documents;
|
|
363
|
+
if (documents === undefined)
|
|
364
|
+
return `<p class="muted">Loading relay documents…</p>`;
|
|
365
|
+
if (documents.kind === "unavailable")
|
|
366
|
+
return renderErrorState("Could not list this relay's documents.", documents.detail);
|
|
367
|
+
if (documents.kind === "missing") {
|
|
368
|
+
return `<div class="empty-state"><strong>This relay no longer exists.</strong><p>Click Refresh to rescan ${escapeHtml(RELAYS_ROOT)}.</p></div>`;
|
|
369
|
+
}
|
|
370
|
+
const partialNotice = documents.partial
|
|
371
|
+
? `<div class="status info">Some nested content is not listed — this relay tree is deeper or larger than the panel lists.</div>`
|
|
372
|
+
: "";
|
|
373
|
+
if (documents.documentCount === 0) {
|
|
374
|
+
return `${partialNotice}
|
|
375
|
+
<div class="empty-state">
|
|
376
|
+
<strong>This relay has no documents yet.</strong>
|
|
377
|
+
<p>Relay packets usually contain <code>status.md</code>, <code>charter.md</code>, and <code>log.md</code>.</p>
|
|
378
|
+
</div>
|
|
379
|
+
`;
|
|
380
|
+
}
|
|
381
|
+
return `${partialNotice}${this.renderSelectedDocument()}`;
|
|
382
|
+
}
|
|
383
|
+
renderSelectedDocument() {
|
|
384
|
+
const documentPath = this.selectedDocumentPath;
|
|
385
|
+
const content = this.documentContent;
|
|
386
|
+
if (documentPath === undefined)
|
|
387
|
+
return `<p class="muted">Select a document.</p>`;
|
|
388
|
+
if (content === undefined)
|
|
389
|
+
return `<p class="muted">Loading ${escapeHtml(documentName(documentPath))}…</p>`;
|
|
390
|
+
if (content.kind === "unavailable")
|
|
391
|
+
return renderErrorState("Could not read this document.", content.detail);
|
|
392
|
+
if (content.kind === "missing") {
|
|
393
|
+
return `<div class="empty-state"><strong>This document no longer exists.</strong><p>Click Refresh to rescan the relay.</p></div>`;
|
|
394
|
+
}
|
|
395
|
+
if (content.binary) {
|
|
396
|
+
return `<div class="empty-state"><strong>Binary file: ${escapeHtml(documentName(documentPath))}</strong><p>Binary documents have no text preview.</p></div>`;
|
|
397
|
+
}
|
|
398
|
+
const truncation = content.truncated
|
|
399
|
+
? `<div class="status info">This document is truncated — only the beginning is shown.</div>`
|
|
400
|
+
: "";
|
|
401
|
+
if (isMarkdownDocumentPath(documentPath)) {
|
|
402
|
+
return `${truncation}<div class="document markdown">${renderRelayDocumentHtml(content.content)}</div>`;
|
|
403
|
+
}
|
|
404
|
+
return `${truncation}<pre class="document">${escapeHtml(content.content)}</pre>`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/** Shell regions come from a literal template; absence means the template broke. */
|
|
408
|
+
function requiredRegion(root, selector) {
|
|
409
|
+
const element = root.querySelector(selector);
|
|
410
|
+
if (!(element instanceof HTMLElement))
|
|
411
|
+
throw new Error(`relays panel shell is missing ${selector}`);
|
|
412
|
+
return element;
|
|
413
|
+
}
|
|
414
|
+
/** Reload glyph matching the app's own refresh control (AppRefreshControl). */
|
|
415
|
+
function refreshIconSvg() {
|
|
416
|
+
return `
|
|
417
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
418
|
+
<path d="M20 6v5h-5"></path>
|
|
419
|
+
<path d="M4 18v-5h5"></path>
|
|
420
|
+
<path d="M18.2 9A7 7 0 0 0 6.1 6.8L4 9"></path>
|
|
421
|
+
<path d="M5.8 15a7 7 0 0 0 12.1 2.2L20 15"></path>
|
|
422
|
+
</svg>
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The folder chip to highlight when the selected document is hidden: the first
|
|
427
|
+
* collapsed ancestor walking from the relay root. Undefined when every
|
|
428
|
+
* ancestor is expanded (the tab is visible) or the file is not in the tree.
|
|
429
|
+
*/
|
|
430
|
+
export function collapsedAncestorOfSelectedFile(tree, filePath, expandedPaths) {
|
|
431
|
+
let hiddenAncestor;
|
|
432
|
+
const visit = (entries) => {
|
|
433
|
+
for (const entry of entries) {
|
|
434
|
+
if (entry.kind === "file") {
|
|
435
|
+
if (entry.path === filePath)
|
|
436
|
+
return true;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (hiddenAncestor === undefined && !expandedPaths.has(entry.path))
|
|
440
|
+
hiddenAncestor = entry.path;
|
|
441
|
+
if (visit(entry.children))
|
|
442
|
+
return true;
|
|
443
|
+
if (hiddenAncestor === entry.path)
|
|
444
|
+
hiddenAncestor = undefined;
|
|
445
|
+
}
|
|
446
|
+
return false;
|
|
447
|
+
};
|
|
448
|
+
return visit(tree) ? hiddenAncestor : undefined;
|
|
449
|
+
}
|
|
450
|
+
function chevronSvg() {
|
|
451
|
+
return `<svg class="chevron" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M6 4l4 4-4 4"></path></svg>`;
|
|
452
|
+
}
|
|
453
|
+
function contextKey(context) {
|
|
454
|
+
return `${context.machine.id}:${context.workspace.projectId}:${context.workspace.id}`;
|
|
455
|
+
}
|
|
456
|
+
function documentName(path) {
|
|
457
|
+
return path.slice(path.lastIndexOf("/") + 1);
|
|
458
|
+
}
|
|
459
|
+
function renderEmptyState() {
|
|
460
|
+
return `
|
|
461
|
+
<div class="empty-state">
|
|
462
|
+
<strong>No relays in this workspace.</strong>
|
|
463
|
+
<p>Relay packets live in <code>${escapeHtml(RELAYS_ROOT)}/<name>/</code>. This workspace has none yet.</p>
|
|
464
|
+
</div>
|
|
465
|
+
`;
|
|
466
|
+
}
|
|
467
|
+
function renderErrorState(message, detail) {
|
|
468
|
+
return `<div class="status error"><strong>${escapeHtml(message)}</strong><pre>${escapeHtml(detail)}</pre></div>`;
|
|
469
|
+
}
|
|
470
|
+
function relaysStyles() {
|
|
471
|
+
return `
|
|
472
|
+
<style>
|
|
473
|
+
:host { display: contents; }
|
|
474
|
+
/* Toolbar and tab strip are panel chrome: they must never flex-shrink
|
|
475
|
+
(the app container is a fixed-height flex column; with shrink enabled
|
|
476
|
+
the viewer's huge content basis starves them down to a sliver once a
|
|
477
|
+
tall document renders). The viewer absorbs all shrinking instead. */
|
|
478
|
+
.toolbar { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--pi-border-muted); }
|
|
479
|
+
.toolbar[hidden], .document-tabs[hidden] { display: none; }
|
|
480
|
+
.toolbar-actions { display: inline-flex; align-items: center; flex-wrap: nowrap; justify-content: flex-end; gap: 8px; min-width: 0; }
|
|
481
|
+
.relay-name { min-width: 0; color: var(--pi-text-secondary); overflow-wrap: anywhere; }
|
|
482
|
+
/* Bottom padding (not viewer margin) so the gap below the tabs persists
|
|
483
|
+
when the viewer's content scrolls up against its top edge. */
|
|
484
|
+
.document-tabs { flex: 0 0 auto; display: flex; flex-wrap: nowrap; gap: 6px; padding: 8px 12px; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scrollbar-width: thin; }
|
|
485
|
+
.viewer { flex: 1 1 auto; box-sizing: border-box; display: grid; align-content: start; gap: 12px; min-height: 0; overflow: auto; padding: 12px; }
|
|
486
|
+
/* Grid children default to min-width: auto; without these caps a wide code
|
|
487
|
+
block or table would silently stretch the whole viewer track. */
|
|
488
|
+
.viewer > * { box-sizing: border-box; min-width: 0; max-width: 100%; }
|
|
489
|
+
button, select { border: 1px solid var(--pi-border); border-radius: 7px; background: var(--pi-surface); color: var(--pi-text); font: inherit; }
|
|
490
|
+
button { cursor: pointer; padding: 6px 10px; }
|
|
491
|
+
button.icon-button { flex: 0 0 auto; display: inline-grid; place-items: center; width: 30px; height: 30px; padding: 0; }
|
|
492
|
+
button.icon-button svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; }
|
|
493
|
+
select { min-width: 0; max-width: 240px; padding: 5px 6px; }
|
|
494
|
+
.document-tab { flex: 0 0 auto; white-space: nowrap; font-size: 12px; padding: 4px 10px; }
|
|
495
|
+
.document-tab.active { border-color: var(--pi-accent-border); background: var(--pi-accent); color: var(--pi-bg); }
|
|
496
|
+
/* An expanded folder wraps its chip and children in one rounded group so
|
|
497
|
+
nested tabs read as contained by their folder; nested groups stack.
|
|
498
|
+
No inner padding: the wrapper's border lines sit flush on the button
|
|
499
|
+
row (a continuous edge), and the -1px vertical margins cancel the
|
|
500
|
+
border's height so expanding never grows the strip or its neighbors. */
|
|
501
|
+
.directory-group { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; min-width: 0; border: 1px solid var(--pi-border-muted); border-radius: 10px; background: var(--pi-bg-overlay-soft); padding: 0; margin: -1px 0; }
|
|
502
|
+
/* Folder chips interleave with file tabs; the chevron rotates while expanded. */
|
|
503
|
+
.directory-tab { display: inline-flex; align-items: center; gap: 5px; }
|
|
504
|
+
.directory-tab .chevron { flex: 0 0 auto; width: 9px; height: 9px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; transition: transform 0.12s ease; }
|
|
505
|
+
.directory-tab[aria-expanded="true"] .chevron { transform: rotate(90deg); }
|
|
506
|
+
/* The selection survives a folder collapse: the nearest collapsed ancestor
|
|
507
|
+
highlights instead, without taking the fill reserved for the open document. */
|
|
508
|
+
.directory-tab.contains-active { border-color: var(--pi-accent-border); color: var(--pi-accent); }
|
|
509
|
+
.directory-tab.contains-active::after { content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--pi-accent); }
|
|
510
|
+
code, pre { border: 1px solid var(--pi-border-muted); border-radius: 6px; background: var(--pi-bg); color: var(--pi-text-secondary); font: 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
511
|
+
code { padding: 2px 5px; }
|
|
512
|
+
pre { margin: 0; overflow: auto; padding: 8px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
513
|
+
.document.markdown { line-height: 1.5; overflow-wrap: anywhere; }
|
|
514
|
+
.document.markdown p, .document.markdown ul, .document.markdown ol, .document.markdown pre, .document.markdown blockquote, .document.markdown .table-scroll { margin: 0 0 10px; }
|
|
515
|
+
.document.markdown > :last-child { margin-bottom: 0; }
|
|
516
|
+
.document.markdown h1, .document.markdown h2, .document.markdown h3, .document.markdown h4 { line-height: 1.25; margin: 14px 0 8px; }
|
|
517
|
+
.document.markdown h1:first-child, .document.markdown h2:first-child, .document.markdown h3:first-child, .document.markdown h4:first-child { margin-top: 0; }
|
|
518
|
+
.document.markdown h1 { font-size: 18px; }
|
|
519
|
+
.document.markdown h2 { font-size: 16px; }
|
|
520
|
+
.document.markdown h3 { font-size: 14px; }
|
|
521
|
+
.document.markdown h4 { font-size: 13px; }
|
|
522
|
+
.document.markdown ul, .document.markdown ol { padding-left: 22px; }
|
|
523
|
+
.document.markdown li + li { margin-top: 3px; }
|
|
524
|
+
.document.markdown pre { white-space: pre; overflow-wrap: normal; }
|
|
525
|
+
.document.markdown pre code { border: 0; background: transparent; padding: 0; }
|
|
526
|
+
.document.markdown img { box-sizing: border-box; max-width: 100%; }
|
|
527
|
+
.document.markdown blockquote { border-left: 3px solid var(--pi-border-muted); color: var(--pi-muted); padding-left: 10px; }
|
|
528
|
+
.document.markdown a { color: var(--pi-accent); }
|
|
529
|
+
.document.markdown .table-scroll { max-width: 100%; overflow-x: auto; }
|
|
530
|
+
/* Cells wrap at word boundaries only: a wide table keeps its natural width
|
|
531
|
+
and scrolls inside .table-scroll instead of being squeezed unreadably. */
|
|
532
|
+
.document.markdown table { border-collapse: collapse; overflow-wrap: normal; }
|
|
533
|
+
.document.markdown th, .document.markdown td { border: 1px solid var(--pi-border-muted); padding: 4px 8px; }
|
|
534
|
+
.status pre { margin-top: 8px; }
|
|
535
|
+
.muted { color: var(--pi-muted); }
|
|
536
|
+
.empty-state { border: 1px dashed var(--pi-border-muted); border-radius: 8px; color: var(--pi-muted); padding: 12px; }
|
|
537
|
+
.empty-state p { margin: 6px 0 0; }
|
|
538
|
+
.status { border: 1px solid var(--pi-border); border-radius: 8px; padding: 10px; }
|
|
539
|
+
.status.info { border-color: var(--pi-accent-border); background: var(--pi-bg-overlay-soft); }
|
|
540
|
+
.status.error { border-color: var(--pi-danger); color: var(--pi-danger); }
|
|
541
|
+
.empty { padding: 16px; color: var(--pi-muted); }
|
|
542
|
+
</style>
|
|
543
|
+
`;
|
|
544
|
+
}
|
|
545
|
+
function escapeHtml(value) {
|
|
546
|
+
return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
547
|
+
}
|
|
548
|
+
function escapeAttr(value) {
|
|
549
|
+
return escapeHtml(value).replaceAll('"', """);
|
|
550
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Vendored dependencies
|
|
2
|
+
|
|
3
|
+
## marked (`marked.esm.js`)
|
|
4
|
+
|
|
5
|
+
- **Source:** `node_modules/marked/lib/marked.esm.js` — marked v18.0.6, the
|
|
6
|
+
version the repo pins (`"marked": "^18.0.6"` in the root `package.json`).
|
|
7
|
+
- **License:** MIT — copyright (c) 2018-2026 MarkedJS, (c) 2011-2018
|
|
8
|
+
Christopher Jeffrey. The attribution header at the top of `marked.esm.js` is
|
|
9
|
+
preserved; see also `node_modules/marked/LICENSE.md`.
|
|
10
|
+
- **Why vendored:** bundled plugins load in the browser as standalone ES
|
|
11
|
+
modules and cannot resolve bare package specifiers. The plugin therefore
|
|
12
|
+
ships the exact marked build the repo already depends on, rather than
|
|
13
|
+
hand-rolling a markdown subset or adding a markdown helper to the plugin API.
|
|
14
|
+
- **Local modification:** the trailing `//# sourceMappingURL=marked.esm.js.map`
|
|
15
|
+
comment was removed because the (much larger) source map is not vendored.
|
|
16
|
+
Everything else is byte-identical to the published file.
|
|
17
|
+
- **`marked.esm.d.ts`** is a hand-written minimal declaration covering only the
|
|
18
|
+
surface `markdownDocument.ts` uses. The plugin build
|
|
19
|
+
(`scripts/build-plugins.mjs`) skips `.d.ts` files and copies `.js` assets
|
|
20
|
+
verbatim, so the vendored module ships as-is.
|
|
21
|
+
|
|
22
|
+
**Updating:** after a `marked` upgrade in the root `package.json`, copy the new
|
|
23
|
+
`node_modules/marked/lib/marked.esm.js` here, re-apply the sourceMappingURL
|
|
24
|
+
removal, and update this note.
|