Consolidate management navigation
Build / build (push) Successful in 11s

This commit is contained in:
2026-07-21 07:42:30 -05:00
parent b4045eefc9
commit c663f523ad
12 changed files with 8286 additions and 147 deletions
+7 -7
View File
@@ -37,19 +37,19 @@ Use `.env.local` for local secrets. Keep `.env.local` and any real token-bearing
- `dist/` is the deployable output directory. Deployment or Chrome load-unpacked flows should point directly at `dist/`. - `dist/` is the deployable output directory. Deployment or Chrome load-unpacked flows should point directly at `dist/`.
- `dist/` is generated output and should not be edited directly. - `dist/` is generated output and should not be edited directly.
- Static extension assets live in `public/`; extension icon sources live under `public/icons/`. - Static extension assets live in `public/`; extension icon sources live under `public/icons/`.
- `scripts/generate-local-context.mjs` generates `public/local-context.json` from `/Users/Tabitha/.openclaw/workspace_Father` before builds. - `scripts/generate-local-context.mjs` generates `public/local-context.json` from `/Users/Tabitha/.openclaw/workspace_Father` and configured local folder search roots before builds.
- `public/local-context.json` is a committed fallback snapshot so CI builds can still produce a self-contained `dist/` if the local workspace root is unavailable. - `public/local-context.json` is a committed fallback snapshot so CI builds can still produce a self-contained `dist/` if the local workspace root is unavailable. It includes generated workspaces, Gitea worktrees, known URLs, and searchable folder candidates for workspace selection.
- `npm run build` runs `scripts/verify-dist.mjs` after Vite to ensure the generated output has the required extension files. - `npm run build` runs `scripts/verify-dist.mjs` after Vite to ensure the generated output has the required extension files.
- Host permissions include `<all_urls>` for Chrome visible-tab screenshot capture plus the Gitea host derived from `VITE_GITEA_BASE_URL`. Rebuild after changing the base URL. - Host permissions include `<all_urls>` for Chrome visible-tab screenshot capture plus the Gitea host derived from `VITE_GITEA_BASE_URL`. Rebuild after changing the base URL.
- Runtime API calls are centralized in `src/api.ts`. - Runtime API calls are centralized in `src/api.ts`.
- Side panel state is centralized in the vanilla Zustand store at `src/store.ts`. - Side panel state is centralized in the vanilla Zustand store at `src/store.ts`.
- The extension opens as a Chrome side panel. It is not a draggable in-page window. - The extension opens as a Chrome side panel. It is not a draggable in-page window.
- The side panel has icon menu sections for Add URL, URLs, Gitea repos, Issues, Add Issue, Agents, Prompts, and Workspaces. Prompts must stay to the right of Agents, and Workspaces must stay to the right of Prompts. - The side panel has top-level toolbar sections for URLs, Gitea repos, Issues, Add Issue, and a management dropdown. Agents, Prompts, and Workspaces must live under the management dropdown rather than as separate toolbar buttons.
- Add URL is enabled only when the active tab's base URL is not already in the URLs list. The user must pick either a workspace or a Gitea repo; the app infers the linked counterpart from local context. - Add URL must live inside the URLs view as a first-row Add URL button that opens a modal dialog. It is enabled only when the active tab's base URL is not already in the URLs list. The user must pick either a workspace or a Gitea repo; the app infers the linked counterpart from local context.
- URLs can be deleted from the URLs list. User-added URLs are removed from storage; generated known URLs are hidden through the stored deleted-base list. - URLs can be deleted from the URLs list. User-added URLs are removed from storage; generated known URLs are hidden through the stored deleted-base list. URL creation must happen through the modal dialog, not through a standalone Add URL menu item.
- The Agents view lists available agent files and must allow adding, editing, and deleting persisted agents from `chrome.storage.local`. At least one agent must remain available. - The Agents view lists available agent files and must allow adding, editing, and deleting persisted agents from `chrome.storage.local`. At least one agent must remain available.
- The default `GLOBAL AGENT` is backed by `AGENTS.md` and contains agnostic handoff rules with template variables such as `{{git}}`, `{{workspace}}`, `{{url}}`, `{{baseUrl}}`, `{{profile}}`, and `{{issue}}`. It must tell downstream agents to close the Gitea issue after the implementation has been pushed, and to leave a Gitea issue comment when delivery is blocked, partial, or risky. - The default `GLOBAL AGENT` is backed by `AGENTS.md` and contains agnostic handoff rules with template variables such as `{{git}}`, `{{workspace}}`, `{{url}}`, `{{baseUrl}}`, `{{profile}}`, and `{{issue}}`. It must tell downstream agents to close the Gitea issue after the implementation has been pushed, and to leave a Gitea issue comment when delivery is blocked, partial, or risky.
- The Workspaces view lists generated workspaces from `workspace_Father` plus user-added workspaces persisted in `chrome.storage.local` under `silmaAide.userWorkspaces`. User-added workspaces require an absolute local path and may be deleted from the side panel. - The Workspaces view lists generated workspaces from `workspace_Father` plus user-added workspaces persisted in `chrome.storage.local` under `silmaAide.userWorkspaces`. It must show only a first-row Add workspace button for creation; that button opens a modal dialog. User-added workspaces must be selected from searchable generated folder candidates rather than by copy/pasting a path, and may be deleted from the side panel.
- The Prompts view manages workspace-scoped default prompts persisted in `chrome.storage.local` under `silmaAide.prompts`. Prompt records must include a name, workspace path, and content. When opening Prompts for a new prompt, the workspace selector should default to the active known URL's workspace/site when available. - The Prompts view manages workspace-scoped default prompts persisted in `chrome.storage.local` under `silmaAide.prompts`. Prompt records must include a name, workspace path, and content. When opening Prompts for a new prompt, the workspace selector should default to the active known URL's workspace/site when available.
- Add Issue is enabled only when the active tab's base URL is already in the URLs list and has a linked Gitea repo. The issue form must show the target repo, the active URL the extension believes it is attached to, let the user select a workspace prompt above Content, let the user select an agent file and issue labels, capture visible-tab screenshots, accept multiple accumulated Gitea-friendly file attachments, and include `Chrome profile: Silma` plus the selected agent in created issue bodies. - Add Issue is enabled only when the active tab's base URL is already in the URLs list and has a linked Gitea repo. The issue form must show the target repo, the active URL the extension believes it is attached to, let the user select a workspace prompt above Content, let the user select an agent file and issue labels, capture visible-tab screenshots, accept multiple accumulated Gitea-friendly file attachments, and include `Chrome profile: Silma` plus the selected agent in created issue bodies.
- The Add Issue default prompt dropdown must only show prompts attached to the active URL's workspace path. Selecting a prompt copies its content into the editable Content field for human adjustment. - The Add Issue default prompt dropdown must only show prompts attached to the active URL's workspace path. Selecting a prompt copies its content into the editable Content field for human adjustment.
@@ -64,7 +64,7 @@ Use `.env.local` for local secrets. Keep `.env.local` and any real token-bearing
- Clicking the selected Issues row again or clicking the detail close control must close the selected issue detail panel without deleting or changing the issue. - Clicking the selected Issues row again or clicking the detail close control must close the selected issue detail panel without deleting or changing the issue.
- Saving selected issue edits must use the selected issue's repo identity from Issues, include Gitea's current issue content version when available, verify the saved subject/content with a readback before reporting success, and must never fall back to the active tab's repo. - Saving selected issue edits must use the selected issue's repo identity from Issues, include Gitea's current issue content version when available, verify the saved subject/content with a readback before reporting success, and must never fall back to the active tab's repo.
- Existing issue rows must allow deleting the issue. Existing issue labels must be editable from the selected issue form and saved through the Gitea issue labels endpoint. Existing issue attachments must be removable, and additional screenshots/files must be uploadable directly to the issue without creating a comment. Deletion must require user confirmation. - Existing issue rows must allow deleting the issue. Existing issue labels must be editable from the selected issue form and saved through the Gitea issue labels endpoint. Existing issue attachments must be removable, and additional screenshots/files must be uploadable directly to the issue without creating a comment. Deletion must require user confirmation.
- Text-entry updates in Add Issue, selected issue edits, Issues comments, Agents, Prompts, and Workspaces must not re-render the focused form control or blur the user while they type. - Text-entry updates in Add Issue, selected issue edits, Issues comments, Agents, Prompts, workspace folder search, and Workspaces must not re-render the focused form control or blur the user while they type.
- Keep the Add Issue view dense; do not show the redundant view header title/description inside this view. - Keep the Add Issue view dense; do not show the redundant view header title/description inside this view.
- The extension requests `activeTab` and `<all_urls>` host permission so screenshots can be captured from the current Chrome tab even after the side panel has stayed open across tab changes. - The extension requests `activeTab` and `<all_urls>` host permission so screenshots can be captured from the current Chrome tab even after the side panel has stayed open across tab changes.
- Gitea API errors should include the failed operation and, for network-level failures, the configured Gitea API base URL plus actionable checks for env, extension host permissions, network/VPN access, token scope, and attachment size. - Gitea API errors should include the failed operation and, for network-level failures, the configured Gitea API base URL plus actionable checks for env, extension host permissions, network/VPN access, token scope, and attachment size.
+2 -2
View File
@@ -26,9 +26,9 @@ The deployable extension output is `dist/`. Load `dist/` in Chrome as an unpacke
Static assets, including the extension icon assets, live in `public/` and are copied into `dist/` during the build. Static assets, including the extension icon assets, live in `public/` and are copied into `dist/` during the build.
The app opens in the Chrome side panel. The top icon menu includes Add URL, URLs, Gitea repos, Issues, Add Issue, Agents, Prompts, and Workspaces. Add URL is enabled only for an active tab base URL that is not already in the URLs list. URLs can be deleted from the list. Workspaces includes generated `workspace_Father` entries plus user-added absolute local paths stored in `chrome.storage.local`. The Prompts view manages workspace-scoped default prompts in `chrome.storage.local`; new prompt workspace selection defaults to the active known URL's workspace when available, and Add Issue shows matching prompts above Content and copies the selected prompt into the editable content field. The Agents view manages persisted agent files, starting with `GLOBAL AGENT` from `AGENTS.md`; agents can use variables such as `{{git}}`, `{{workspace}}`, and `{{url}}` in their markdown content. The default global agent tells downstream agents to close the Gitea issue after pushing the implementation and to comment when delivery is blocked, partial, or risky. Add Issue is enabled when the active tab base URL is already linked to a Gitea repo; it shows the active URL being attached, creates an issue with the selected agent and selected labels, writes requirements under `# Requirements` and metadata under `# References`, and uploads the rendered agent file plus accumulated screenshots and files as issue assets. Captured screenshots can be expanded, removed, or cropped with a wider no-horizontal-scroll crop panel. Issues lists recent issues by creation time, defaults to open issues for the current repo, can expand to all loaded repos, has Open/Closed status checkboxes plus an empty-by-default label filter, and lets a selected issue open directly in Gitea, log a placeholder Send to Codex payload, edit subject/content/labels against that issue's repo with content-version readback verification, view attachments and comments, upload more screenshots/files directly to the issue, and add a comment with screenshots and file attachments. Clicking the selected issue row again or the detail close control collapses the detail panel. Rows can be deleted, and existing issue attachments can be deleted. Gitea API errors include the failed operation and network-level failures identify the configured API base plus common checks. The app opens in the Chrome side panel. The top toolbar includes URLs, Gitea repos, Issues, Add Issue, and a management dropdown for Agents, Prompts, and Workspaces. URLs and Workspaces each have a first-row Add button that opens a modal dialog. Add URL is enabled only for an active tab base URL that is not already in the URLs list. URLs can be deleted from the list. Workspaces includes generated `workspace_Father` entries plus user-added absolute local paths stored in `chrome.storage.local`; workspace creation uses a searchable generated folder index instead of copy/paste path entry. The Prompts view manages workspace-scoped default prompts in `chrome.storage.local`; new prompt workspace selection defaults to the active known URL's workspace when available, and Add Issue shows matching prompts above Content and copies the selected prompt into the editable content field. The Agents view manages persisted agent files, starting with `GLOBAL AGENT` from `AGENTS.md`; agents can use variables such as `{{git}}`, `{{workspace}}`, and `{{url}}` in their markdown content. The default global agent tells downstream agents to close the Gitea issue after pushing the implementation and to comment when delivery is blocked, partial, or risky. Add Issue is enabled when the active tab base URL is already linked to a Gitea repo; it shows the active URL being attached, creates an issue with the selected agent and selected labels, writes requirements under `# Requirements` and metadata under `# References`, and uploads the rendered agent file plus accumulated screenshots and files as issue assets. Captured screenshots can be expanded, removed, or cropped with a wider no-horizontal-scroll crop panel. Issues lists recent issues by creation time, defaults to open issues for the current repo, can expand to all loaded repos, has Open/Closed status checkboxes plus an empty-by-default label filter, and lets a selected issue open directly in Gitea, log a placeholder Send to Codex payload, edit subject/content/labels against that issue's repo with content-version readback verification, view attachments and comments, upload more screenshots/files directly to the issue, and add a comment with screenshots and file attachments. Clicking the selected issue row again or the detail close control collapses the detail panel. Rows can be deleted, and existing issue attachments can be deleted. Gitea API errors include the failed operation and network-level failures identify the configured API base plus common checks.
Builds generate `public/local-context.json` from `/Users/Tabitha/.openclaw/workspace_Father`. That snapshot links known workspaces to local Gitea repos and is copied into `dist/` so the built extension is self-contained. Builds generate `public/local-context.json` from `/Users/Tabitha/.openclaw/workspace_Father` plus searchable local folder candidates. That snapshot links known workspaces to local Gitea repos and is copied into `dist/` so the built extension is self-contained.
## Extension Management ## Extension Management
+7 -5
View File
@@ -69,20 +69,21 @@ Required variables:
- Additional selected-issue screenshots/files can also be uploaded directly as issue assets without creating a comment. - Additional selected-issue screenshots/files can also be uploaded directly as issue assets without creating a comment.
- Existing issue attachments can be deleted from the selected issue. - Existing issue attachments can be deleted from the selected issue.
- Existing issue rows can delete the issue. Issue labels are edited in the selected issue form. Deletion must require confirmation. - Existing issue rows can delete the issue. Issue labels are edited in the selected issue form. Deletion must require confirmation.
- Typing in Add Issue, selected issue edit fields, Issues comments, Agents editor fields, or Prompts editor fields must not blur the focused control. - Typing in Add Issue, selected issue edit fields, Issues comments, Agents editor fields, Prompts editor fields, or workspace folder search must not blur the focused control.
## Prompt Management ## Prompt Management
- The Prompts menu item is workspace-scoped and stores prompt records in `silmaAide.prompts`. - Agents, Prompts, and Workspaces must be selected from a management dropdown, not separate top-level toolbar buttons.
- The Prompts view is workspace-scoped and stores prompt records in `silmaAide.prompts`.
- The Prompts view must allow adding, editing, and deleting prompts with a name, workspace path, and content. - The Prompts view must allow adding, editing, and deleting prompts with a name, workspace path, and content.
- New prompt workspace selection should default to the active known URL's workspace/site when available. - New prompt workspace selection should default to the active known URL's workspace/site when available.
- Prompts must remain to the right of Agents in the toolbar.
## Workspace Management ## Workspace Management
- Workspaces must remain to the right of Prompts in the toolbar.
- The Workspaces view shows generated workspaces from `workspace_Father` plus user-added workspaces from `silmaAide.userWorkspaces`. - The Workspaces view shows generated workspaces from `workspace_Father` plus user-added workspaces from `silmaAide.userWorkspaces`.
- User-added workspaces require an absolute local path and can be deleted from the side panel. - The Workspaces view must show only a first-row Add workspace button for creation. That button opens a modal dialog.
- User-added workspaces require an absolute local path selected from searchable generated folder candidates in `local-context.json`; do not require copy/paste path entry.
- User-added workspaces can be deleted from the side panel.
- Add URL workspace selection should include both generated and user-added workspaces. - Add URL workspace selection should include both generated and user-added workspaces.
## Gitea Error Reporting ## Gitea Error Reporting
@@ -93,6 +94,7 @@ Required variables:
## URL Management ## URL Management
- URLs can be deleted from the URLs list. - URLs can be deleted from the URLs list.
- Add URL must live inside the URLs view as a first-row Add URL button that opens a modal dialog.
- Deleting a user-added URL removes it from `silmaAide.userUrls` in `chrome.storage.local`. - Deleting a user-added URL removes it from `silmaAide.userUrls` in `chrome.storage.local`.
- Deleting a generated known URL stores its base URL in `silmaAide.deletedUrlBases` so it is hidden without editing generated local context. - Deleting a generated known URL stores its base URL in `silmaAide.deletedUrlBases` so it is hidden without editing generated local context.
+8 -15
View File
@@ -10,10 +10,6 @@
<main class="side-panel-shell"> <main class="side-panel-shell">
<section class="app-panel" aria-label="Silma AI Aide side panel"> <section class="app-panel" aria-label="Silma AI Aide side panel">
<nav class="toolbar" aria-label="Aide sections"> <nav class="toolbar" aria-label="Aide sections">
<button id="add-url" class="tool-button" type="button" data-view="add-url" disabled title="Add URL">
<i data-lucide="link"></i>
<span>Add URL</span>
</button>
<button class="tool-button" type="button" data-view="urls" title="URLs"> <button class="tool-button" type="button" data-view="urls" title="URLs">
<i data-lucide="list"></i> <i data-lucide="list"></i>
<span>URLs</span> <span>URLs</span>
@@ -30,18 +26,15 @@
<i data-lucide="circle-plus"></i> <i data-lucide="circle-plus"></i>
<span>Add Issue</span> <span>Add Issue</span>
</button> </button>
<button class="tool-button" type="button" data-view="agents" title="Agents"> <label class="tool-select-label" title="Management">
<i data-lucide="bot"></i> <i data-lucide="bot"></i>
<span>Agents</span> <select id="manage-view-select" aria-label="Management section">
</button> <option value="">Manage</option>
<button class="tool-button" type="button" data-view="prompts" title="Prompts"> <option value="agents">Agents</option>
<i data-lucide="file-text"></i> <option value="prompts">Prompts</option>
<span>Prompts</span> <option value="workspaces">Workspaces</option>
</button> </select>
<button class="tool-button" type="button" data-view="workspaces" title="Workspaces"> </label>
<i data-lucide="folder-git-2"></i>
<span>Workspaces</span>
</button>
</nav> </nav>
<section class="content-panel"> <section class="content-panel">
+2 -2
View File
@@ -15,11 +15,11 @@ Important files:
- `index.html`: Side panel document. - `index.html`: Side panel document.
- `public/icons/`: Extension icon source and generated PNG sizes copied into `dist/` by Vite. - `public/icons/`: Extension icon source and generated PNG sizes copied into `dist/` by Vite.
- `public/local-context.json`: Generated workspace/repo/URL context copied into `dist/`. - `public/local-context.json`: Generated workspace/repo/URL context copied into `dist/`.
- `scripts/generate-local-context.mjs`: Scans `/Users/Tabitha/.openclaw/workspace_Father` and writes `public/local-context.json`. - `scripts/generate-local-context.mjs`: Scans `/Users/Tabitha/.openclaw/workspace_Father` plus configured local folder search roots and writes `public/local-context.json`.
Build with `npm run build`. The deployable output directory is `dist/`; load that folder in Chrome as an unpacked extension or point deployment tooling at it. Do not edit `dist/` directly. Build with `npm run build`. The deployable output directory is `dist/`; load that folder in Chrome as an unpacked extension or point deployment tooling at it. Do not edit `dist/` directly.
UI behavior: the app renders as a Chrome side panel, not a draggable window. The menu items are Add URL, URLs, Gitea Repos, Issues, Add Issue, Agents, Prompts, and Workspaces; Prompts stay to the right of Agents, and Workspaces stays to the right of Prompts. Add URL is enabled only when the active tab base URL is not already known; the user must choose either a workspace or a Gitea repo, and the app infers the linked counterpart. URLs can be deleted from the list; user-added URLs are removed from storage, while generated known URLs are hidden through stored deleted bases. Workspaces combines generated `workspace_Father` entries with user-added absolute local paths stored under `silmaAide.userWorkspaces`. The Prompts view manages workspace-scoped default prompts in `chrome.storage.local` under `silmaAide.prompts`; new prompt workspace selection defaults to the active known URL's workspace/site when available, and Add Issue shows prompts matching the active URL workspace above Content and copies the selected prompt into the editable Content field for human adjustment. The Agents view manages persisted agent files and starts with `GLOBAL AGENT` backed by `AGENTS.md`; agent markdown can use `{{git}}`, `{{repo}}`, `{{workspace}}`, `{{url}}`, `{{baseUrl}}`, `{{profile}}`, and `{{issue}}` template variables. The default global agent tells downstream agents to close the Gitea issue after pushing the implementation and to leave a Gitea issue comment when delivery is blocked, partial, or risky. Add Issue is enabled only when the active tab base URL is known and linked to a Gitea repo. It shows the target repo and active URL, captures visible-tab screenshots with thumbnails, accepts accumulated file attachments, lets the user select an agent file and issue labels, creates the Gitea issue with the selected labels, and uploads the rendered agent file plus all screenshots/files. The label options are Change Request, Idea, Tabitha, Orson, Belisarius, Ozymandias, and Ready; new issues default to Change Request. Captured screenshots can be expanded, removed, or cropped with a wider no-horizontal-scroll draggable/resizable crop panel. Created issue bodies put the human-entered idea/change request under `# Requirements` and generated metadata under `# References`, including `Chrome profile: Silma` and the selected agent. Issues lists recent issues by creation time, defaults to open issues for the active URL's linked repo through an `Only current repo` checkbox, can load recent issues across all loaded repos, supports Open/Closed status multiselect filtering, has an empty-by-default label filter, and shows selected issue subject/content/label edit fields that save against the selected issue repo with Gitea content-version readback verification, a direct Open in Gitea link, a placeholder Send to Codex console log action, attachments, comment chains, direct selected-issue screenshot/file attachment uploads, and a comment form with text, screenshots, and file attachments. Clicking the selected issue row again or the detail close control collapses the detail panel. Existing issue rows can be deleted after confirmation, and existing issue attachments can be deleted. Text-entry state updates must not blur focused Add Issue, selected issue edit, Issues comment, Agents editor controls, Prompts editor controls, or Workspaces editor controls. Gitea API errors include the failed operation and network-level failures identify the configured API base with env, permissions, network/VPN, token scope, and attachment-size checks. Gitea repos are ordered by latest default-branch commit. Workspace and URL views show established links between local workspaces, known URLs, and Gitea repos. UI behavior: the app renders as a Chrome side panel, not a draggable window. The top toolbar items are URLs, Gitea Repos, Issues, Add Issue, and a management dropdown for Agents, Prompts, and Workspaces. Add URL lives inside the URLs view as a first-row button that opens a modal dialog; it is enabled only when the active tab base URL is not already known. The user must choose either a workspace or a Gitea repo, and the app infers the linked counterpart. URLs can be deleted from the list; user-added URLs are removed from storage, while generated known URLs are hidden through stored deleted bases. Workspaces combines generated `workspace_Father` entries with user-added absolute local paths stored under `silmaAide.userWorkspaces`; workspace creation uses a first-row Add workspace button, a modal dialog, and searchable generated folder candidates instead of copy/paste path entry. The Prompts view manages workspace-scoped default prompts in `chrome.storage.local` under `silmaAide.prompts`; new prompt workspace selection defaults to the active known URL's workspace/site when available, and Add Issue shows prompts matching the active URL workspace above Content and copies the selected prompt into the editable Content field for human adjustment. The Agents view manages persisted agent files and starts with `GLOBAL AGENT` backed by `AGENTS.md`; agent markdown can use `{{git}}`, `{{repo}}`, `{{workspace}}`, `{{url}}`, `{{baseUrl}}`, `{{profile}}`, and `{{issue}}` template variables. The default global agent tells downstream agents to close the Gitea issue after pushing the implementation and to leave a Gitea issue comment when delivery is blocked, partial, or risky. Add Issue is enabled only when the active tab base URL is known and linked to a Gitea repo. It shows the target repo and active URL, captures visible-tab screenshots with thumbnails, accepts accumulated file attachments, lets the user select an agent file and issue labels, creates the Gitea issue with the selected labels, and uploads the rendered agent file plus all screenshots/files. The label options are Change Request, Idea, Tabitha, Orson, Belisarius, Ozymandias, and Ready; new issues default to Change Request. Captured screenshots can be expanded, removed, or cropped with a wider no-horizontal-scroll draggable/resizable crop panel. Created issue bodies put the human-entered idea/change request under `# Requirements` and generated metadata under `# References`, including `Chrome profile: Silma` and the selected agent. Issues lists recent issues by creation time, defaults to open issues for the active URL's linked repo through an `Only current repo` checkbox, can load recent issues across all loaded repos, supports Open/Closed status multiselect filtering, has an empty-by-default label filter, and shows selected issue subject/content/label edit fields that save against the selected issue repo with Gitea content-version readback verification, a direct Open in Gitea link, a placeholder Send to Codex console log action, attachments, comment chains, direct selected-issue screenshot/file attachment uploads, and a comment form with text, screenshots, and file attachments. Clicking the selected issue row again or the detail close control collapses the detail panel. Existing issue rows can be deleted after confirmation, and existing issue attachments can be deleted. Text-entry state updates must not blur focused Add Issue, selected issue edit, Issues comment, Agents editor controls, Prompts editor controls, or workspace folder search controls. Gitea API errors include the failed operation and network-level failures identify the configured API base with env, permissions, network/VPN, token scope, and attachment-size checks. Gitea repos are ordered by latest default-branch commit. Workspace and URL views show established links between local workspaces, known URLs, and Gitea repos.
Extension management rule: use the Codex Chrome extension with the Chrome profile named `Silma`. Do not use `Profile 1`. Extension management rule: use the Codex Chrome extension with the Chrome profile named `Silma`. Do not use `Profile 1`.
+9 -19
View File
@@ -20,19 +20,17 @@
"vite_config": "vite.config.ts", "vite_config": "vite.config.ts",
"extension_icons": "public/icons/", "extension_icons": "public/icons/",
"local_context": "public/local-context.json", "local_context": "public/local-context.json",
"local_context_folder_candidates": "public/local-context.json#folderCandidates",
"local_context_generator": "scripts/generate-local-context.mjs", "local_context_generator": "scripts/generate-local-context.mjs",
"generated_manifest": "dist/manifest.json" "generated_manifest": "dist/manifest.json"
}, },
"ui": { "ui": {
"shell": "Chrome side panel", "shell": "Chrome side panel",
"menu_items": [ "menu_items": [
{
"name": "Add URL",
"enabled": "when active tab base URL is not already known"
},
{ {
"name": "URLs", "name": "URLs",
"enabled": true "enabled": true,
"contains": "first-row Add URL modal trigger"
}, },
{ {
"name": "Gitea Repos", "name": "Gitea Repos",
@@ -47,25 +45,17 @@
"enabled": "when active tab base URL is known and linked to a Gitea repo" "enabled": "when active tab base URL is known and linked to a Gitea repo"
}, },
{ {
"name": "Agents", "name": "Management",
"enabled": true
},
{
"name": "Prompts",
"enabled": true, "enabled": true,
"position": "right of Agents" "type": "dropdown",
}, "options": ["Agents", "Prompts", "Workspaces"]
{
"name": "Workspaces",
"enabled": true,
"position": "right of Prompts"
} }
], ],
"repo_ordering": "Gitea repos are ordered by latest default-branch commit timestamp.", "repo_ordering": "Gitea repos are ordered by latest default-branch commit timestamp.",
"agents_behavior": "The Agents view manages persisted agent files. The initial agent is GLOBAL AGENT backed by AGENTS.md, and agent markdown supports {{git}}, {{repo}}, {{workspace}}, {{url}}, {{baseUrl}}, {{profile}}, and {{issue}} template variables. The default global agent tells downstream agents to close the Gitea issue after pushing the implementation and to leave a Gitea issue comment when delivery is blocked, partial, or risky; persisted built-in global agents should pick up these required completion rules without replacing unrelated custom text.", "agents_behavior": "The Agents view manages persisted agent files. The initial agent is GLOBAL AGENT backed by AGENTS.md, and agent markdown supports {{git}}, {{repo}}, {{workspace}}, {{url}}, {{baseUrl}}, {{profile}}, and {{issue}} template variables. The default global agent tells downstream agents to close the Gitea issue after pushing the implementation and to leave a Gitea issue comment when delivery is blocked, partial, or risky; persisted built-in global agents should pick up these required completion rules without replacing unrelated custom text.",
"prompts_behavior": "The Prompts view manages workspace-scoped prompt records persisted in chrome.storage.local under silmaAide.prompts. Each prompt has a name, workspace path, and content. New prompt workspace selection defaults to the active known URL's workspace/site when available. Add Issue shows prompts matching the active known URL's workspace path immediately above Content, and selecting one copies prompt content into the editable Content field for human adjustment.", "prompts_behavior": "The Prompts view is selected from the Management dropdown and manages workspace-scoped prompt records persisted in chrome.storage.local under silmaAide.prompts. Each prompt has a name, workspace path, and content. New prompt workspace selection defaults to the active known URL's workspace/site when available. Add Issue shows prompts matching the active known URL's workspace path immediately above Content, and selecting one copies prompt content into the editable Content field for human adjustment.",
"workspaces_behavior": "The Workspaces view combines generated workspace_Father entries with user-added absolute local paths persisted in chrome.storage.local under silmaAide.userWorkspaces. User-added workspaces can be deleted from the side panel.", "workspaces_behavior": "The Workspaces view is selected from the Management dropdown and combines generated workspace_Father entries with user-added absolute local paths persisted in chrome.storage.local under silmaAide.userWorkspaces. Workspace creation uses a first-row Add workspace button, a modal dialog, and searchable generated folder candidates from local-context.json instead of copy/paste path entry. User-added workspaces can be deleted from the side panel.",
"add_url_behavior": "The user must pick either a workspace or a Gitea repo. The app infers the linked counterpart from generated and user-added workspace context and stores the URL link in chrome.storage.local.", "add_url_behavior": "Add URL lives inside the URLs view as a first-row button that opens a modal dialog. The user must pick either a workspace or a Gitea repo. The app infers the linked counterpart from generated and user-added workspace context and stores the URL link in chrome.storage.local.",
"delete_url_behavior": "URLs can be deleted from the list. User-added URLs are removed from chrome.storage.local; generated known URLs are hidden by storing their base URL in silmaAide.deletedUrlBases.", "delete_url_behavior": "URLs can be deleted from the list. User-added URLs are removed from chrome.storage.local; generated known URLs are hidden by storing their base URL in silmaAide.deletedUrlBases.",
"add_issue_behavior": "The form shows the target repo and active URL for the active known URL, lets the user select a workspace prompt, agent file, and issue labels, captures visible-tab screenshots with thumbnails, accepts accumulated file attachments, creates a Gitea issue with the selected labels, and uploads the rendered agent file plus all screenshots/files as issue assets. Label options are Change Request, Idea, Tabitha, Orson, Belisarius, Ozymandias, and Ready; new issues default to Change Request. Captured screenshots can be expanded, removed, or cropped with a wider no-horizontal-scroll draggable/resizable crop panel. Issue bodies put human-entered idea/change request content under # Requirements and generated metadata under # References, including Chrome profile: Silma and the selected agent. The view intentionally omits the redundant Add Issue header copy.", "add_issue_behavior": "The form shows the target repo and active URL for the active known URL, lets the user select a workspace prompt, agent file, and issue labels, captures visible-tab screenshots with thumbnails, accepts accumulated file attachments, creates a Gitea issue with the selected labels, and uploads the rendered agent file plus all screenshots/files as issue assets. Label options are Change Request, Idea, Tabitha, Orson, Belisarius, Ozymandias, and Ready; new issues default to Change Request. Captured screenshots can be expanded, removed, or cropped with a wider no-horizontal-scroll draggable/resizable crop panel. Issue bodies put human-entered idea/change request content under # Requirements and generated metadata under # References, including Chrome profile: Silma and the selected agent. The view intentionally omits the redundant Add Issue header copy.",
"open_issues_behavior": "Issues lists recent issues ordered by creation time. The Only current repo checkbox defaults to true and limits rows to the active URL's linked Gitea repo; disabling it loads recent issues from all repos already loaded by the extension. Status filtering uses Open and Closed checkboxes as a multiselect with Open checked by default. Label filtering defaults to empty and filters visible rows by a selected known label.", "open_issues_behavior": "Issues lists recent issues ordered by creation time. The Only current repo checkbox defaults to true and limits rows to the active URL's linked Gitea repo; disabling it loads recent issues from all repos already loaded by the extension. Status filtering uses Open and Closed checkboxes as a multiselect with Open checked by default. Label filtering defaults to empty and filters visible rows by a selected known label.",
File diff suppressed because it is too large Load Diff
+65
View File
@@ -6,6 +6,24 @@ const workspaceRoot =
process.env.WORKSPACE_FATHER_PATH || "/Users/Tabitha/.openclaw/workspace_Father"; process.env.WORKSPACE_FATHER_PATH || "/Users/Tabitha/.openclaw/workspace_Father";
const outputPath = join(process.cwd(), "public", "local-context.json"); const outputPath = join(process.cwd(), "public", "local-context.json");
const giteaBaseUrl = "http://gitea.orson.tealthrone"; const giteaBaseUrl = "http://gitea.orson.tealthrone";
const folderSearchRoots = (process.env.WORKSPACE_FOLDER_SEARCH_ROOTS || `/Users/Tabitha,${workspaceRoot}`)
.split(",")
.map((root) => root.trim())
.filter(Boolean);
const folderSearchMaxDepth = Number(process.env.WORKSPACE_FOLDER_SEARCH_MAX_DEPTH || 3);
const skippedFolderNames = new Set([
".git",
".Trash",
"Applications",
"Library",
"Movies",
"Music",
"node_modules",
"dist",
"build",
".next",
".vite"
]);
function runGit(args, cwd) { function runGit(args, cwd) {
try { try {
@@ -47,6 +65,52 @@ function findGitWorktrees(root, maxDepth = 4) {
return found.sort(); return found.sort();
} }
function findFolderCandidates(roots, maxDepth = 3) {
const found = new Map();
function addCandidate(path, source) {
found.set(path, {
name: basename(path),
path,
source
});
}
function walk(dir, depth, source) {
if (depth > maxDepth || found.has(dir)) {
return;
}
addCandidate(dir, source);
let entries = [];
try {
entries = readdirSync(dir, { withFileTypes: true });
} catch {
return;
}
for (const entry of entries) {
if (!entry.isDirectory() || skippedFolderNames.has(entry.name)) {
continue;
}
walk(join(dir, entry.name), depth + 1, source);
}
}
for (const root of roots) {
if (!existsSync(root)) {
continue;
}
walk(root, 0, root === workspaceRoot ? "workspace-root" : "folder-search");
}
return Array.from(found.values())
.filter((candidate) => candidate.path !== "/Users/Tabitha")
.sort((a, b) => a.path.localeCompare(b.path))
.slice(0, 2000);
}
function normalizeGiteaRemote(remoteUrl) { function normalizeGiteaRemote(remoteUrl) {
if (!remoteUrl) { if (!remoteUrl) {
return null; return null;
@@ -128,6 +192,7 @@ function buildContext() {
workspaceRoot, workspaceRoot,
workspaces, workspaces,
gitWorktrees, gitWorktrees,
folderCandidates: findFolderCandidates(folderSearchRoots, folderSearchMaxDepth),
knownUrls: [ knownUrls: [
{ {
label: "Automated Training Monitor", label: "Automated Training Monitor",
+7
View File
@@ -23,6 +23,12 @@ export interface LocalGitWorktree {
giteaRepo: LocalGiteaRepoLink | null; giteaRepo: LocalGiteaRepoLink | null;
} }
export interface LocalFolderCandidate {
name: string;
path: string;
source: "workspace-root" | "folder-search";
}
export interface KnownUrl { export interface KnownUrl {
label: string; label: string;
url: string; url: string;
@@ -37,6 +43,7 @@ export interface LocalContext {
workspaceRoot: string; workspaceRoot: string;
workspaces: LocalWorkspace[]; workspaces: LocalWorkspace[];
gitWorktrees: LocalGitWorktree[]; gitWorktrees: LocalGitWorktree[];
folderCandidates: LocalFolderCandidate[];
knownUrls: KnownUrl[]; knownUrls: KnownUrl[];
} }
+237 -93
View File
@@ -29,14 +29,14 @@ import {
type WorkspacePrompt type WorkspacePrompt
} from "./store"; } from "./store";
import type { GiteaIssueAttachment, GiteaIssueComment, GiteaIssueSummary, GiteaRepoSummary } from "./api"; import type { GiteaIssueAttachment, GiteaIssueComment, GiteaIssueSummary, GiteaRepoSummary } from "./api";
import type { KnownUrl, LocalContext, LocalWorkspace } from "./localContext"; import type { KnownUrl, LocalContext, LocalFolderCandidate, LocalWorkspace } from "./localContext";
type CropHandle = "n" | "e" | "s" | "w" | "nw" | "ne" | "sw" | "se"; type CropHandle = "n" | "e" | "s" | "w" | "nw" | "ne" | "sw" | "se";
const status = document.querySelector<HTMLElement>("#status"); const status = document.querySelector<HTMLElement>("#status");
const refreshData = document.querySelector<HTMLButtonElement>("#refresh-data"); const refreshData = document.querySelector<HTMLButtonElement>("#refresh-data");
const addUrlButton = document.querySelector<HTMLButtonElement>("#add-url");
const addIssueButton = document.querySelector<HTMLButtonElement>("#add-issue"); const addIssueButton = document.querySelector<HTMLButtonElement>("#add-issue");
const manageViewSelect = document.querySelector<HTMLSelectElement>("#manage-view-select");
const viewHeader = document.querySelector<HTMLElement>("#view-header"); const viewHeader = document.querySelector<HTMLElement>("#view-header");
const viewContent = document.querySelector<HTMLElement>("#view-content"); const viewContent = document.querySelector<HTMLElement>("#view-content");
const viewButtons = Array.from(document.querySelectorAll<HTMLButtonElement>("[data-view]")); const viewButtons = Array.from(document.querySelectorAll<HTMLButtonElement>("[data-view]"));
@@ -185,66 +185,49 @@ function renderRepos(repos: GiteaRepoSummary[]): string {
function renderWorkspaces(workspaces: LocalWorkspace[]): string { function renderWorkspaces(workspaces: LocalWorkspace[]): string {
const state = appStore.getState(); const state = appStore.getState();
const userWorkspacePaths = new Set(state.userWorkspaces.map((workspace) => workspace.path)); const userWorkspacePaths = new Set(state.userWorkspaces.map((workspace) => workspace.path));
const draft = state.workspaceDraft; const workspaceList = workspaces.length
? `
<div class="compact-list">
${workspaces
.map((workspace) => {
const links = workspace.linkedRepos.length
? workspace.linkedRepos
.map(
(repo) =>
`<a class="repo-link" href="${repo.webUrl}" target="_blank" rel="noreferrer">${escapeHtml(repo.fullName)}</a>`
)
.join("")
: `<span class="muted">No Gitea repo link established</span>`;
if (!workspaces.length) { return `
return ` <article class="workspace-row">
<div class="empty-state">No workspaces found in workspace_Father.</div> <div class="row-main">
${renderWorkspaceEditor(draft)} <h2 class="item-title">${escapeHtml(workspace.name)}</h2>
`; <p class="path-text">${escapeHtml(workspace.path)}</p>
} </div>
<div class="repo-link-list">${links}</div>
return ` <div class="workspace-actions">
<div class="compact-list"> ${
${workspaces userWorkspacePaths.has(workspace.path)
.map((workspace) => { ? `<button class="danger-button compact-button" type="button" data-delete-user-workspace="${escapeHtml(workspace.path)}">Delete</button>`
const links = workspace.linkedRepos.length : `<span class="muted">Generated</span>`
? workspace.linkedRepos }
.map( </div>
(repo) => </article>
`<a class="repo-link" href="${repo.webUrl}" target="_blank" rel="noreferrer">${escapeHtml(repo.fullName)}</a>` `;
) })
.join("") .join("")}
: `<span class="muted">No Gitea repo link established</span>`;
return `
<article class="workspace-row">
<div class="row-main">
<h2 class="item-title">${escapeHtml(workspace.name)}</h2>
<p class="path-text">${escapeHtml(workspace.path)}</p>
</div>
<div class="repo-link-list">${links}</div>
<div class="workspace-actions">
${
userWorkspacePaths.has(workspace.path)
? `<button class="danger-button compact-button" type="button" data-delete-user-workspace="${escapeHtml(workspace.path)}">Delete</button>`
: `<span class="muted">Generated</span>`
}
</div>
</article>
`;
})
.join("")}
</div>
${renderWorkspaceEditor(draft)}
`;
}
function renderWorkspaceEditor(draft: { name: string; path: string }): string {
return `
<article class="item-card workspace-editor-card">
<div class="form-stack">
<label>
<span>Name</span>
<input id="workspace-name" type="text" value="${escapeHtml(draft.name)}" placeholder="workspace-name" />
</label>
<label>
<span>Path</span>
<input id="workspace-path" type="text" value="${escapeHtml(draft.path)}" placeholder="/Users/Tabitha/path/to/workspace" />
</label>
<button id="save-workspace" class="primary-button" type="button" ${draft.path.trim() ? "" : "disabled"}>Add workspace</button>
</div> </div>
</article> `
: `<div class="empty-state">No workspaces found.</div>`;
return `
<div class="view-action-row">
<button id="open-workspace-dialog" class="secondary-button compact-button" type="button">
Add workspace
</button>
</div>
${workspaceList}
`; `;
} }
@@ -368,35 +351,45 @@ function renderPrompts(prompts: WorkspacePrompt[]): string {
} }
function renderUrls(urls: KnownUrl[]): string { function renderUrls(urls: KnownUrl[]): string {
if (!urls.length) { const state = appStore.getState();
return `<div class="empty-state">No known URLs registered.</div>`; const canOpenAddUrl = Boolean(state.activeBaseUrl && !isActiveBaseUrlKnown());
} const urlList = urls.length
? `
<div class="compact-list">
${urls
.map(
(knownUrl) => `
<article class="url-row">
<div class="row-main">
<h2 class="item-title">${escapeHtml(knownUrl.label)}</h2>
<a class="url-text" href="${knownUrl.url}" target="_blank" rel="noreferrer">${escapeHtml(knownUrl.url)}</a>
<span class="path-text">${escapeHtml(knownUrl.workspacePath)}</span>
${
knownUrl.giteaRepo
? `<a class="repo-link" href="${knownUrl.giteaRepo.webUrl}" target="_blank" rel="noreferrer">${escapeHtml(knownUrl.giteaRepo.fullName)}</a>`
: `<span class="muted">No Gitea repo link established</span>`
}
</div>
<div class="url-actions">
<button class="visit-button" type="button" data-open-url="${knownUrl.url}">Quick visit</button>
<button class="danger-button" type="button" data-delete-url="${knownUrl.url}">Delete</button>
</div>
</article>
`
)
.join("")}
</div>
`
: `<div class="empty-state">No known URLs registered.</div>`;
return ` return `
<div class="compact-list"> <div class="view-action-row">
${urls <button id="open-add-url-dialog" class="secondary-button compact-button" type="button" ${canOpenAddUrl ? "" : "disabled"}>
.map( Add URL
(knownUrl) => ` </button>
<article class="url-row"> <span class="muted">${escapeHtml(state.activeBaseUrl || "Open an http or https tab to add a URL.")}</span>
<div class="row-main">
<h2 class="item-title">${escapeHtml(knownUrl.label)}</h2>
<a class="url-text" href="${knownUrl.url}" target="_blank" rel="noreferrer">${escapeHtml(knownUrl.url)}</a>
<span class="path-text">${escapeHtml(knownUrl.workspacePath)}</span>
${
knownUrl.giteaRepo
? `<a class="repo-link" href="${knownUrl.giteaRepo.webUrl}" target="_blank" rel="noreferrer">${escapeHtml(knownUrl.giteaRepo.fullName)}</a>`
: `<span class="muted">No Gitea repo link established</span>`
}
</div>
<div class="url-actions">
<button class="visit-button" type="button" data-open-url="${knownUrl.url}">Quick visit</button>
<button class="danger-button" type="button" data-delete-url="${knownUrl.url}">Delete</button>
</div>
</article>
`
)
.join("")}
</div> </div>
${urlList}
`; `;
} }
@@ -484,6 +477,103 @@ function renderAddUrl(): string {
`; `;
} }
function workspaceFolderCandidates(): LocalFolderCandidate[] {
const state = appStore.getState();
const workspaceCandidates = getAllWorkspaces(state).map((workspace) => ({
name: workspace.name,
path: workspace.path,
source: "workspace-root" as const
}));
const contextCandidates = state.localContext?.folderCandidates ?? [];
const candidates = [...workspaceCandidates, ...contextCandidates];
return candidates
.filter((candidate, index) => candidates.findIndex((item) => item.path === candidate.path) === index)
.sort((a, b) => a.path.localeCompare(b.path));
}
function renderWorkspaceSearchResults(candidates: LocalFolderCandidate[], query: string): string {
const normalizedQuery = query.trim().toLowerCase();
const matches = (normalizedQuery
? candidates.filter(
(candidate) =>
candidate.name.toLowerCase().includes(normalizedQuery) ||
candidate.path.toLowerCase().includes(normalizedQuery)
)
: candidates
).slice(0, 40);
if (!matches.length) {
return `<div class="empty-state">No matching folders found in the generated folder index.</div>`;
}
return `
<div class="folder-result-list">
${matches
.map(
(candidate) => `
<button class="folder-result" type="button" data-select-workspace-folder="${escapeHtml(candidate.path)}" data-folder-name="${escapeHtml(candidate.name)}">
<span>${escapeHtml(candidate.name)}</span>
<span class="path-text">${escapeHtml(candidate.path)}</span>
</button>
`
)
.join("")}
</div>
`;
}
function renderWorkspaceDialog(): string {
const state = appStore.getState();
const draft = state.workspaceDraft;
return `
<div class="form-stack">
<label>
<span>Folder search</span>
<input id="workspace-search" type="text" value="${escapeHtml(draft.search)}" placeholder="Search folders by name or path" />
</label>
<div id="workspace-folder-results">
${renderWorkspaceSearchResults(workspaceFolderCandidates(), draft.search)}
</div>
<label>
<span>Name</span>
<input id="workspace-name" type="text" value="${escapeHtml(draft.name)}" placeholder="Workspace name" />
</label>
<label>
<span>Selected folder</span>
<input id="workspace-path" type="text" value="${escapeHtml(draft.path)}" readonly placeholder="Select a folder from search results" />
</label>
<button id="save-workspace" class="primary-button" type="button" ${draft.path.trim() ? "" : "disabled"}>Add workspace</button>
</div>
`;
}
function renderActiveDialog(): string {
const state = appStore.getState();
if (!state.activeDialog) {
return "";
}
const title = state.activeDialog === "add-url" ? "Add URL" : "Add workspace";
const body = state.activeDialog === "add-url" ? renderAddUrl() : renderWorkspaceDialog();
return `
<div class="app-modal" role="dialog" aria-modal="true" aria-label="${escapeHtml(title)}">
<article class="app-modal-panel">
<div class="app-modal-heading">
<h2>${escapeHtml(title)}</h2>
<button class="secondary-button compact-button icon-only-button" type="button" data-close-app-dialog title="Close" aria-label="Close">
<i data-lucide="x"></i>
</button>
</div>
${body}
</article>
</div>
`;
}
function renderFileList(files: File[], target: "issue" | "comment" | "issue-attachment"): string { function renderFileList(files: File[], target: "issue" | "comment" | "issue-attachment"): string {
if (!files.length) { if (!files.length) {
return `<span class="muted">No files selected.</span>`; return `<span class="muted">No files selected.</span>`;
@@ -1025,7 +1115,35 @@ function openUrl(url: string): void {
window.open(url, "_blank", "noopener,noreferrer"); window.open(url, "_blank", "noopener,noreferrer");
} }
function bindWorkspaceFolderResultActions(): void {
viewContent?.querySelectorAll<HTMLButtonElement>("[data-select-workspace-folder]").forEach((button) => {
button.addEventListener("click", () => {
const path = button.dataset.selectWorkspaceFolder;
const name = button.dataset.folderName;
if (path) {
appStore.getState().setWorkspaceDraft({
path,
name: appStore.getState().workspaceDraft.name || name || ""
});
}
});
});
}
function bindViewActions(): void { function bindViewActions(): void {
viewContent?.querySelector<HTMLButtonElement>("#open-add-url-dialog")?.addEventListener("click", () => {
appStore.getState().openDialog("add-url");
});
viewContent?.querySelector<HTMLButtonElement>("#open-workspace-dialog")?.addEventListener("click", () => {
appStore.getState().openDialog("add-workspace");
});
viewContent?.querySelector<HTMLButtonElement>("[data-close-app-dialog]")?.addEventListener("click", () => {
appStore.getState().closeDialog();
});
viewContent?.querySelector<HTMLInputElement>("#agent-name")?.addEventListener("input", (event) => { viewContent?.querySelector<HTMLInputElement>("#agent-name")?.addEventListener("input", (event) => {
appStore.getState().setAgentDraft({ appStore.getState().setAgentDraft({
name: (event.target as HTMLInputElement).value name: (event.target as HTMLInputElement).value
@@ -1082,12 +1200,14 @@ function bindViewActions(): void {
}); });
}); });
viewContent?.querySelector<HTMLInputElement>("#workspace-path")?.addEventListener("input", (event) => { viewContent?.querySelector<HTMLInputElement>("#workspace-search")?.addEventListener("input", (event) => {
appStore.getState().setWorkspaceDraft({ appStore.getState().setWorkspaceDraft({
path: (event.target as HTMLInputElement).value search: (event.target as HTMLInputElement).value
}); });
}); });
bindWorkspaceFolderResultActions();
viewContent?.querySelector<HTMLButtonElement>("#save-workspace")?.addEventListener("click", () => { viewContent?.querySelector<HTMLButtonElement>("#save-workspace")?.addEventListener("click", () => {
void appStore.getState().saveWorkspace(); void appStore.getState().saveWorkspace();
}); });
@@ -1566,6 +1686,7 @@ function renderView(): void {
viewContent.innerHTML = renderUrls(allUrls()); viewContent.innerHTML = renderUrls(allUrls());
} }
viewContent.insertAdjacentHTML("beforeend", renderActiveDialog());
viewContent.insertAdjacentHTML("beforeend", renderScreenshotPreview()); viewContent.insertAdjacentHTML("beforeend", renderScreenshotPreview());
hydrateIcons(); hydrateIcons();
bindViewActions(); bindViewActions();
@@ -1583,14 +1704,14 @@ function renderChrome(): void {
refreshData.disabled = state.isLoadingData; refreshData.disabled = state.isLoadingData;
} }
if (addUrlButton) {
addUrlButton.disabled = !state.activeBaseUrl || isActiveBaseUrlKnown();
}
if (addIssueButton) { if (addIssueButton) {
addIssueButton.disabled = !canAddIssue(); addIssueButton.disabled = !canAddIssue();
} }
if (manageViewSelect) {
manageViewSelect.value = ["agents", "prompts", "workspaces"].includes(state.activeView) ? state.activeView : "";
}
viewButtons.forEach((button) => { viewButtons.forEach((button) => {
const view = button.dataset.view; const view = button.dataset.view;
button.dataset.active = view === state.activeView ? "true" : "false"; button.dataset.active = view === state.activeView ? "true" : "false";
@@ -1605,7 +1726,7 @@ function shouldRenderView(): boolean {
} }
return !activeElement.matches( return !activeElement.matches(
"#issue-subject, #issue-content, #issue-agent, #selected-issue-subject, #selected-issue-content, #issue-comment-content, #agent-name, #agent-file, #agent-description, #agent-content, #prompt-name, #prompt-content, #workspace-name, #workspace-path, [data-label-target]" "#issue-subject, #issue-content, #issue-agent, #selected-issue-subject, #selected-issue-content, #issue-comment-content, #agent-name, #agent-file, #agent-description, #agent-content, #prompt-name, #prompt-content, #workspace-name, #workspace-search, [data-label-target]"
); );
} }
@@ -1615,6 +1736,9 @@ function syncFocusedFormControls(): void {
const saveSelectedIssue = viewContent?.querySelector<HTMLButtonElement>("#save-selected-issue"); const saveSelectedIssue = viewContent?.querySelector<HTMLButtonElement>("#save-selected-issue");
const submitComment = viewContent?.querySelector<HTMLButtonElement>("#submit-issue-comment"); const submitComment = viewContent?.querySelector<HTMLButtonElement>("#submit-issue-comment");
const saveWorkspace = viewContent?.querySelector<HTMLButtonElement>("#save-workspace"); const saveWorkspace = viewContent?.querySelector<HTMLButtonElement>("#save-workspace");
const workspaceResults = viewContent?.querySelector<HTMLElement>("#workspace-folder-results");
const workspacePath = viewContent?.querySelector<HTMLInputElement>("#workspace-path");
const workspaceName = viewContent?.querySelector<HTMLInputElement>("#workspace-name");
if (createIssue) { if (createIssue) {
createIssue.disabled = !state.issueDraft.subject.trim() || state.isCreatingIssue; createIssue.disabled = !state.issueDraft.subject.trim() || state.isCreatingIssue;
@@ -1632,6 +1756,19 @@ function syncFocusedFormControls(): void {
saveWorkspace.disabled = !state.workspaceDraft.path.trim(); saveWorkspace.disabled = !state.workspaceDraft.path.trim();
} }
if (workspaceResults) {
workspaceResults.innerHTML = renderWorkspaceSearchResults(workspaceFolderCandidates(), state.workspaceDraft.search);
bindWorkspaceFolderResultActions();
}
if (workspacePath) {
workspacePath.value = state.workspaceDraft.path;
}
if (workspaceName && document.activeElement !== workspaceName) {
workspaceName.value = state.workspaceDraft.name;
}
if (saveSelectedIssue && state.selectedIssueDetail) { if (saveSelectedIssue && state.selectedIssueDetail) {
const detailLabels = ISSUE_LABEL_OPTIONS.filter((label) => const detailLabels = ISSUE_LABEL_OPTIONS.filter((label) =>
state.selectedIssueDetail?.issue.labels.some((issueLabel) => issueLabel.toLowerCase() === label.toLowerCase()) state.selectedIssueDetail?.issue.labels.some((issueLabel) => issueLabel.toLowerCase() === label.toLowerCase())
@@ -1693,6 +1830,13 @@ viewButtons.forEach((button) => {
}); });
}); });
manageViewSelect?.addEventListener("change", () => {
const view = manageViewSelect.value as AppView;
if (view === "agents" || view === "prompts" || view === "workspaces") {
appStore.getState().setActiveView(view);
}
});
refreshData?.addEventListener("click", () => { refreshData?.addEventListener("click", () => {
void appStore.getState().loadData(); void appStore.getState().loadData();
}); });
+33 -3
View File
@@ -28,6 +28,7 @@ export type AppView = "urls" | "repos" | "workspaces" | "prompts" | "agents" | "
export type AddUrlMode = "workspace" | "repo"; export type AddUrlMode = "workspace" | "repo";
export type ScreenshotTarget = "issue" | "comment" | "issue-attachment"; export type ScreenshotTarget = "issue" | "comment" | "issue-attachment";
export type OpenIssuesStatusFilter = "open" | "closed"; export type OpenIssuesStatusFilter = "open" | "closed";
export type AppDialog = "add-url" | "add-workspace";
export const CHROME_PROFILE_NAME = "Silma"; export const CHROME_PROFILE_NAME = "Silma";
export const ISSUE_LABEL_OPTIONS = [ export const ISSUE_LABEL_OPTIONS = [
@@ -123,6 +124,7 @@ interface AddUrlDraft {
interface WorkspaceDraft { interface WorkspaceDraft {
name: string; name: string;
path: string; path: string;
search: string;
} }
export interface ScreenshotAttachment { export interface ScreenshotAttachment {
@@ -182,6 +184,7 @@ interface ScreenshotPreview {
interface AppState { interface AppState {
activeView: AppView; activeView: AppView;
activeDialog: AppDialog | null;
config: typeof giteaConfig; config: typeof giteaConfig;
connectedUser: GiteaUser | null; connectedUser: GiteaUser | null;
repos: GiteaRepoSummary[]; repos: GiteaRepoSummary[];
@@ -230,6 +233,8 @@ interface AppState {
statusTone: StatusTone; statusTone: StatusTone;
clearStatus: () => void; clearStatus: () => void;
setActiveView: (view: AppView) => void; setActiveView: (view: AppView) => void;
openDialog: (dialog: AppDialog) => void;
closeDialog: () => void;
setAddUrlDraft: (draft: Partial<AddUrlDraft>) => void; setAddUrlDraft: (draft: Partial<AddUrlDraft>) => void;
setWorkspaceDraft: (draft: Partial<WorkspaceDraft>) => void; setWorkspaceDraft: (draft: Partial<WorkspaceDraft>) => void;
saveWorkspace: () => Promise<void>; saveWorkspace: () => Promise<void>;
@@ -853,6 +858,7 @@ function buildIssueCommentBody(state: AppState, knownUrl: KnownUrl | null, attac
export const appStore = createStore<AppState>()((set, get) => ({ export const appStore = createStore<AppState>()((set, get) => ({
activeView: "repos", activeView: "repos",
activeDialog: null,
config: giteaConfig, config: giteaConfig,
connectedUser: null, connectedUser: null,
repos: [], repos: [],
@@ -868,7 +874,8 @@ export const appStore = createStore<AppState>()((set, get) => ({
}, },
workspaceDraft: { workspaceDraft: {
name: "", name: "",
path: "" path: "",
search: ""
}, },
issueDraft: { issueDraft: {
subject: "", subject: "",
@@ -942,6 +949,26 @@ export const appStore = createStore<AppState>()((set, get) => ({
} }
: state.promptDraft : state.promptDraft
})), })),
openDialog: (dialog) =>
set((state) => ({
activeDialog: dialog,
addUrlDraft:
dialog === "add-url"
? {
mode: null,
selectedValue: ""
}
: state.addUrlDraft,
workspaceDraft:
dialog === "add-workspace"
? {
name: "",
path: "",
search: findKnownUrlForBase(state)?.workspaceName ?? ""
}
: state.workspaceDraft
})),
closeDialog: () => set({ activeDialog: null }),
setAddUrlDraft: (draft) => setAddUrlDraft: (draft) =>
set((state) => ({ set((state) => ({
addUrlDraft: { addUrlDraft: {
@@ -982,8 +1009,10 @@ export const appStore = createStore<AppState>()((set, get) => ({
userWorkspaces, userWorkspaces,
workspaceDraft: { workspaceDraft: {
name: "", name: "",
path: "" path: "",
} search: ""
},
activeDialog: null
}); });
setStatus(set, `Added workspace ${name}.`, "success"); setStatus(set, `Added workspace ${name}.`, "success");
}, },
@@ -2243,6 +2272,7 @@ export const appStore = createStore<AppState>()((set, get) => ({
userUrls, userUrls,
deletedUrlBases, deletedUrlBases,
activeView: "urls", activeView: "urls",
activeDialog: null,
addUrlDraft: { mode: null, selectedValue: "" } addUrlDraft: { mode: null, selectedValue: "" }
}); });
setStatus(set, `Linked ${state.activeBaseUrl} to ${link.giteaRepo?.fullName || link.workspaceName}.`, "success"); setStatus(set, `Linked ${state.activeBaseUrl} to ${link.giteaRepo?.fullName || link.workspaceName}.`, "success");
+107 -1
View File
@@ -62,7 +62,7 @@ h1 {
.toolbar { .toolbar {
display: grid; display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr)); grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 1px; gap: 1px;
border-bottom: 1px solid #d9e2ec; border-bottom: 1px solid #d9e2ec;
background: #d9e2ec; background: #d9e2ec;
@@ -114,6 +114,35 @@ h1 {
opacity: 0.42; opacity: 0.42;
} }
.tool-select-label {
display: grid;
grid-template-rows: auto auto;
justify-items: center;
gap: 3px;
min-width: 0;
min-height: 50px;
padding: 7px 4px;
background: #ffffff;
color: #334e68;
}
.tool-select-label svg {
width: 17px;
height: 17px;
stroke-width: 2.3;
}
.tool-select-label select {
min-height: 18px;
border: 0;
padding: 0 2px;
background: transparent;
color: #334e68;
font-size: 10px;
font-weight: 800;
text-align: center;
}
.content-panel { .content-panel {
display: grid; display: grid;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
@@ -162,6 +191,17 @@ h1 {
gap: 0; gap: 0;
} }
.view-action-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
border: 1px solid #d9e2ec;
border-radius: 7px;
padding: 6px;
background: #ffffff;
}
.repo-row { .repo-row {
display: grid; display: grid;
grid-template-columns: minmax(150px, 1fr) 70px 58px minmax(86px, 0.8fr); grid-template-columns: minmax(150px, 1fr) 70px 58px minmax(86px, 0.8fr);
@@ -231,6 +271,72 @@ h1 {
background: #ffffff; background: #ffffff;
} }
.app-modal {
position: fixed;
inset: 0;
z-index: 40;
display: grid;
align-items: start;
justify-items: center;
overflow-y: auto;
padding: 18px 10px;
background: rgb(16 42 67 / 0.26);
}
.app-modal-panel {
display: grid;
gap: 10px;
width: min(100%, 520px);
border: 1px solid #bcccdc;
border-radius: 8px;
padding: 10px;
background: #ffffff;
box-shadow: 0 18px 45px rgb(16 42 67 / 0.18);
}
.app-modal-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.app-modal-heading h2 {
color: #102a43;
font-size: 14px;
line-height: 1.2;
}
.folder-result-list {
display: grid;
max-height: 220px;
overflow-y: auto;
border: 1px solid #d9e2ec;
border-radius: 7px;
}
.folder-result {
display: grid;
gap: 2px;
min-width: 0;
border: 0;
border-bottom: 1px solid #edf2f7;
padding: 6px 7px;
background: #ffffff;
color: #102a43;
cursor: pointer;
text-align: left;
}
.folder-result:last-child {
border-bottom: 0;
}
.folder-result:hover,
.folder-result:focus {
background: #e6f6ff;
}
.dense-card { .dense-card {
gap: 7px; gap: 7px;
} }