Skip to content

feat(dashboard): sticky search bar with GitHub syntax for workflow definitions#42360

Closed
Copilot wants to merge 3 commits into
mainfrom
copilot/update-github-extension-dashboard
Closed

feat(dashboard): sticky search bar with GitHub syntax for workflow definitions#42360
Copilot wants to merge 3 commits into
mainfrom
copilot/update-github-extension-dashboard

Conversation

Copilot AI commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

The Workflows tab had no way to filter definitions — with large workflow lists, finding a specific workflow required manually scanning. Adds a sticky, localStorage-persisted search bar supporting GitHub-style qualifier syntax.

Search syntax

Qualifiers are AND-combined; bare terms match across all fields:

Query Matches
claude name, engine, or label containing "claude"
name:ci-doctor workflow name contains "ci-doctor"
engine:claude engine_id contains "claude"
label:production any label contains "production"
engine:claude label:prod both conditions must hold

Unknown qualifiers fall back to text search on the value. All matching is case-insensitive.

Changes

  • src/app.ts

    • parseDefinitionsSearch(query) — tokenises query into typed SearchTerm[]; handles quoted values (label:"my tag")
    • matchesDefinitionSearch(definition, query) — applies terms against workflow, engine_id, labels
    • definitionsSearch state — initialised from localStorage at component creation time so queries survive reloads
    • applyDefinitionsSearch() — persists to localStorage, resets to page 1
    • loadDefinitionPage() — now paginates filteredDefinitions() instead of raw definitions
  • web/index.html — sticky search bar between Box-header and scroll area; "Clear" button when active; header counter shows 3 / 12 format when filtered; empty-state message adapts to indicate no search matches vs no workflows installed

  • web/styles.css.awd-search-bar with position: sticky; top: 0; z-index: 2

Copilot AI and others added 2 commits June 29, 2026 22:28
- Add GitHub search syntax parser (name:, engine:, label: qualifiers)
- Add definitionsSearch state initialized from localStorage
- Filter definitions before paginating; persist query across sessions
- Add sticky search bar UI with clear button to the Workflows tab
- Rebuild web/app.js from updated src/app.ts

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
- Clarify rawValue extraction comment (match[3] = quoted content, match[2] = unquoted value)
- Unknown qualifiers now fall back to text search on the value instead of literal field:value
- Fix HTML tag structure in Box-title (no more Prettier ></span> collapse)
- Update placeholder to document supported qualifiers: name:, engine:, label:

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title feat: add search bar to workflow definitions view in dashboard feat(dashboard): sticky search bar with GitHub syntax for workflow definitions Jun 29, 2026
Copilot AI requested a review from pelikhan June 29, 2026 22:33
import { paginate } from "./pagination.js";
import type { ReportWindow } from "./dashboard-config.js";

const DEFINITIONS_SEARCH_KEY = "awd-definitions-search";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot move search related code to search.ts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — DEFINITIONS_SEARCH_KEY, SearchTerm, parseDefinitionsSearch, and matchesDefinitionSearch have been moved to src/search.ts, and app.ts now imports from it. Committed in the latest push.

@pelikhan pelikhan marked this pull request as ready for review June 29, 2026 22:37
Copilot AI review requested due to automatic review settings June 29, 2026 22:37
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer failed during the skills-based review.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

No test files were added or modified in this PR. Test Quality Sentinel skipped. The PR only changes dashboard UI files: app.ts, app.js, index.html, and styles.css.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a persistent, qualifier-aware search experience to the Agentic Workflows Dashboard “Workflow definitions” view to make large definition lists easier to navigate, including UI updates and filtering/pagination integration.

Changes:

  • Introduces parsing + matching logic for GitHub-style qualifier queries (name:, engine:, label:) and applies filtering before pagination.
  • Adds a sticky search bar UI with a “Clear” affordance, plus a filtered counter display and updated empty-state messaging.
  • Persists the definitions search query via localStorage to survive reloads.
Show a summary per file
File Description
.github/extensions/agentic-workflows-dashboard/src/app.ts Adds definitions search parsing/matching, filtering, pagination integration, and localStorage persistence.
.github/extensions/agentic-workflows-dashboard/web/index.html Adds the search bar UI, filtered counter formatting, and revised empty-state messaging.
.github/extensions/agentic-workflows-dashboard/web/styles.css Styles the new sticky search bar container.
.github/extensions/agentic-workflows-dashboard/web/app.js Updates the generated/bundled dashboard script to include the new search behavior.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 4
  • Review effort level: Low

Comment on lines +76 to +92
<h2 class="Box-title">
Workflow definitions <span class="Counter ml-1" x-text="definitionsPaged.totalItems"></span>
<span class="color-fg-muted text-small ml-1" x-cloak x-show="definitionsSearch && definitionsPaged.totalItems !== definitions.length">/ <span x-text="definitions.length"></span></span>
</h2>
</div>
<!-- search bar -->
<div class="awd-search-bar">
<div class="d-flex flex-items-center gap-2">
<input
class="form-control input-sm flex-1"
type="search"
placeholder="Search: name:workflow, engine:claude, label:tag, or bare text"
aria-label="Search workflow definitions"
x-model="definitionsSearch"
@input="applyDefinitionsSearch()" />
<button class="btn btn-sm" x-cloak x-show="definitionsSearch" @click="definitionsSearch = ''; applyDefinitionsSearch()">Clear</button>
</div>
Comment on lines +118 to +120
<div class="Box-row color-fg-muted text-center py-3" x-show="!loadingDefinitions && definitionsPaged.totalItems === 0">
<span x-show="!definitionsSearch">No workflow definitions found. Run <code>make build</code> then open this canvas again.</span><span x-show="definitionsSearch">No workflows match your search.</span>
</div>
Comment on lines +263 to +269
definitionsSearch: (() => {
try {
return localStorage.getItem(DEFINITIONS_SEARCH_KEY) ?? "";
} catch {
return "";
}
})(),
Comment on lines +469 to 476
applyDefinitionsSearch() {
try {
localStorage.setItem(DEFINITIONS_SEARCH_KEY, this.definitionsSearch);
} catch {
// localStorage unavailable (e.g. private browsing restrictions)
}
this.loadDefinitionPage(1);
},
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan June 29, 2026 22:48

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: sticky search bar for workflow definitions

The feature is well-designed — localStorage persistence, x-cloak on the counter/clear button, and graceful try/catch around storage access are all the right calls. Three non-blocking suggestions inline:

Summary of findings
# File Severity Issue
1 src/app.ts line 18-19 Low Misleading comment for match[2]; .replace() is dead code
2 web/index.html line 119 Low Empty-state inner <span> elements lack x-cloak (brief double-flash)
3 web/index.html line 90 Low localStorage.setItem fires on every keystroke; suggest .debounce.300ms

None of these block merging — the core logic is correct and the UX is a clear improvement over no search at all.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 62.9 AIC · ⌖ 9.08 AIC · ⊞ 4.9K

if (match[1]) {
const field = match[1].toLowerCase();
const rawValue = match[3] ?? match[2] ?? ""; // match[3]: content inside quotes; match[2]: unquoted value
const value = rawValue.replace(/^"|"$/g, "").toLowerCase();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on the previous line says match[2]: unquoted value, but when the quoted branch of the regex matches, match[2] is actually the full quoted string (e.g. "foo bar" with quotes included). match[3] is the content inside the quotes, so the ?? chain correctly prefers it.

As a consequence, the .replace(/^"|"$/g, "") on this line is effectively dead code — match[3] is already quote-free, and a bare unquoted match[2] won't have surrounding quotes to strip.

Suggested fix:

// match[3] = content inside quotes; match[2] = full value (quoted or bare)
const rawValue = match[3] ?? match[2] ?? "";
const value = rawValue.toLowerCase();

@copilot please address this.

</template>
<div class="Box-row color-fg-muted text-center py-3" x-show="!loadingDefinitions && definitionsPaged.totalItems === 0">No workflow definitions found. Run <code>make build</code> then open this canvas again.</div>
<div class="Box-row color-fg-muted text-center py-3" x-show="!loadingDefinitions && definitionsPaged.totalItems === 0">
<span x-show="!definitionsSearch">No workflow definitions found. Run <code>make build</code> then open this canvas again.</span><span x-show="definitionsSearch">No workflows match your search.</span>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both inner <span> elements lack x-cloak, so during Alpine's initialization phase they are both briefly visible together — the user sees both messages stacked for a flash.

Add x-cloak to each span so Alpine hides them until it has evaluated the x-show conditions:

<span x-cloak x-show="!definitionsSearch">No workflow definitions found. Run <code>make build</code> then open this canvas again.</span><span x-cloak x-show="definitionsSearch">No workflows match your search.</span>

@copilot please address this.

placeholder="Search: name:workflow, engine:claude, label:tag, or bare text"
aria-label="Search workflow definitions"
x-model="definitionsSearch"
@input="applyDefinitionsSearch()" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applyDefinitionsSearch() calls localStorage.setItem on every keystroke. For a search box that will be typed into rapidly this means many synchronous writes — localStorage writes block the main thread.

Use Alpine's built-in debounce modifier to throttle:

`@input`.debounce.300ms="applyDefinitionsSearch()"

This persists the last value after 300 ms of idle time instead of on every character.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — Three medium issues need fixing before merge: redundant parse-per-definition, missing debounce, and tab counter inconsistency. One low-severity dead-code cleanup is also flagged.

### Blocking themes

Performance — query reparsed per definition (medium)
matchesDefinitionSearch calls parseDefinitionsSearch inside a .filter() loop, so the regex tokenizer runs once per workflow definition instead of once per search query. Combined with the un-debounced @input, this creates O(N × keystrokes) work where O(1 × keystrokes + N) is correct. Fixable by hoisting parseDefinitionsSearch into filteredDefinitions().

Input handling — no debounce (medium)
@input fires applyDefinitionsSearch() synchronously on every character, triggering a localStorage write + full filter + paginate on each keystroke. Alpine 3's @input.debounce.200ms modifier fixes this with one word change.

UX correctness — tab badge out of sync (medium)
tabCount returns this.definitions.length regardless of active search. A user who filters down to 3 of 12 workflows sees "12" on the tab badge but "3 / 12" in the header — the counts disagree about what's currently shown.

Dead code — rawValue.replace(...) (low)
The replace is always a no-op given the regex group semantics. Should be removed to avoid misleading future readers.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • patchdiff.githubusercontent.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "patchdiff.githubusercontent.com"

See Network Configuration for more information.

🔎 Code quality review by PR Code Quality Reviewer · 84.4 AIC · ⌖ 7.76 AIC · ⊞ 5.2K
Comment /review to run again

Comments that could not be inline-anchored

.github/extensions/agentic-workflows-dashboard/src/app.ts:35

parseDefinitionsSearch runs once per definition, not once per query — this makes filteredDefinitions() O(N × parse) when it should be O(parse + N × match), causing avoidable UI jank as the definitions list grows.

<details>
<summary>💡 Suggested fix</summary>

Hoist the parse out of matchesDefinitionSearch and into filteredDefinitions(), then pass the already-parsed terms:

function matchesDefinitionTerms(definition: WorkflowDefinition, terms: SearchTerm[]): boolean {
  const 

</details>

<details><summary>.github/extensions/agentic-workflows-dashboard/web/index.html:90</summary>

**No debounce on search input causes a localStorage write + full re-filter on every keystroke**  `applyDefinitionsSearch()` is synchronous, writes to `localStorage`, then calls `loadDefinitionPage(1)`, which re-filters all definitions. Rapid typing fires this work for each character without any delay.

&lt;details&gt;
&lt;summary&gt;💡 Suggested fix&lt;/summary&gt;

Alpine 3 ships a native debounce event modifier  use it:

```html
`@input`.debounce.200ms=&quot;applyDefinitionsSearch()&quot;

This single change batch…

.github/extensions/agentic-workflows-dashboard/src/app.ts:454

tabCount returns unfiltered definitions.length while the inline counter shows the filtered count — when a search is active, the tab badge shows the total definition count but the box header shows filtered / total. This inconsistency will confuse users who see "12" on the tab but "3 / 12" inside the panel.

<details>
<summary>💡 Suggested fix</summary>

tabCount(tab) {
  if (tab.counter === &quot;definitions&quot;) {
    return this.definitionsSearch ? this.filteredDefinitions().length : t…

</details>

<details><summary>.github/extensions/agentic-workflows-dashboard/src/app.ts:19</summary>

**`rawValue.replace(/^&quot;|&quot;$/g, &quot;&quot;)` is dead code**  `match[3]` already holds the unquoted inner content of a quoted string, and `match[2]` for an unquoted token has no surrounding quotes. The replace never strips anything and implies quote-handling coverage that isn&#39;t actually tested.

&lt;details&gt;
&lt;summary&gt;💡 Suggested simplification&lt;/summary&gt;

Given the regex groups:
- `label:&quot;my tag&quot;`  `match[3]` = `my tag` (no quotes)  replace is a no-op
- `engine:claude`  `match[2]` = `claude` (no quotes) 

</details>

@pelikhan pelikhan closed this Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants