Repository navigation
feat(gui): page polish — Providers, Logs, Subagents, Combos, Routing #3397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -98,7 +98,6 @@ export default function ProviderOverviewDashboard({ | |||||||||||||
| <div className="pws-dashboard-header"> | ||||||||||||||
| <div className="pws-dashboard-header-text"> | ||||||||||||||
| <h2 className="pws-dashboard-title">{t("pws.dashboard.title")}</h2> | ||||||||||||||
| <p className="muted pws-dashboard-subtitle">{t("pws.dashboard.subtitle")}</p> | ||||||||||||||
| </div> | ||||||||||||||
| {onEditConfig && ( | ||||||||||||||
| <button type="button" className="btn btn-ghost btn-sm" onClick={onEditConfig}> | ||||||||||||||
|
|
@@ -193,12 +192,12 @@ export default function ProviderOverviewDashboard({ | |||||||||||||
| )} | ||||||||||||||
| </section> | ||||||||||||||
|
|
||||||||||||||
| <section | ||||||||||||||
| <details | ||||||||||||||
| className="pws-dashboard-section pws-dashboard-section--recent" | ||||||||||||||
|
Comment on lines
195
to
196
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When this new disclosure is closed, its Useful? React with 👍 / 👎. |
||||||||||||||
| aria-label={t("pws.dashboard.recentlyUsed")} | ||||||||||||||
| aria-busy={usageLoading || undefined} | ||||||||||||||
| > | ||||||||||||||
| <h3 className="pws-dashboard-section-title">{t("pws.dashboard.recentlyUsed")}</h3> | ||||||||||||||
| <summary className="pws-dashboard-section-title">{t("pws.dashboard.recentlyUsed")}</summary> | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Preserve heading semantics for the recently-used disclosure.
As per coding guidelines, preserve accessibility: keyboard operation, labels, focus behavior, semantic controls, and readable validation errors. Suggested fix- <summary className="pws-dashboard-section-title">{t("pws.dashboard.recentlyUsed")}</summary>
+ <summary className="pws-dashboard-section-title">
+ <span role="heading" aria-level={3}>
+ {t("pws.dashboard.recentlyUsed")}
+ </span>
+ </summary>📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||
| {mostUsed.length > 0 ? ( | ||||||||||||||
| <div className="pws-dashboard-rows"> | ||||||||||||||
| {mostUsed.map(provider => ( | ||||||||||||||
|
|
@@ -230,7 +229,7 @@ export default function ProviderOverviewDashboard({ | |||||||||||||
| ) : ( | ||||||||||||||
| <p className="muted pws-dashboard-empty">{t("pws.dashboard.noUsage")}</p> | ||||||||||||||
| )} | ||||||||||||||
| </section> | ||||||||||||||
| </details> | ||||||||||||||
| </div> | ||||||||||||||
| </div> | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1012,6 +1012,8 @@ export default function RoutingProfiles({ | |
| </form> | ||
| ) : null} | ||
|
|
||
| {/* A dry-run form is dead weight until there is a profile to evaluate. */} | ||
| {draft && ( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the user starts creating a profile, Useful? React with 👍 / 👎. |
||
| <div className="panel" style={{ marginTop: 14, display: "flex", flexDirection: "column", gap: 10 }}> | ||
| <h3>{t("routing.dryRun")}</h3> | ||
| <label className="field-label" htmlFor="routing-context"> | ||
|
|
@@ -1094,6 +1096,8 @@ export default function RoutingProfiles({ | |
| ) : null} | ||
| </div> | ||
|
|
||
| )} | ||
| {profiles.length > 0 && ( | ||
| <div className="panel" style={{ marginTop: 14, display: "flex", flexDirection: "column", gap: 10 }}> | ||
| <h3>{t("routing.analytics")}</h3> | ||
| {analytics ? ( | ||
|
|
@@ -1134,6 +1138,7 @@ export default function RoutingProfiles({ | |
| <p className="muted">{t("routing.analyticsEmpty")}</p> | ||
| )} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { describe, expect, test } from "bun:test"; | ||
| import { readFileSync } from "node:fs"; | ||
| import { join } from "node:path"; | ||
| import { interpolate } from "../src/i18n/shared"; | ||
| import { ko } from "../src/i18n/ko"; | ||
|
|
||
| const read = (p: string) => readFileSync(join(import.meta.dir, p), "utf8"); | ||
| const providers = read("../src/components/provider-workspace/ProviderOverviewDashboard.tsx"); | ||
| const logs = read("../src/pages/Logs.tsx"); | ||
| const subagents = read("../src/components/subagents-workspace/SubagentsWorkspace.tsx"); | ||
| const delegation = read("../src/components/subagents-workspace/SubagentDelegationSection.tsx"); | ||
| const combos = read("../src/components/ComboWorkspace.tsx"); | ||
| const routing = read("../src/pages/RoutingProfiles.tsx"); | ||
|
|
||
| /** devlog/_plan/260904_dashboard_minimal/080_page_polish.md — the small items on five pages. */ | ||
| describe("page polish", () => { | ||
| test("Providers: no overview subtitle; recent-usage folds into a details", () => { | ||
| expect(providers).not.toContain('{t("pws.dashboard.subtitle")}'); | ||
| expect(providers).toContain('<details\n className="pws-dashboard-section pws-dashboard-section--recent"'); | ||
| expect(providers).toContain('<summary className="pws-dashboard-section-title">{t("pws.dashboard.recentlyUsed")}</summary>'); | ||
| }); | ||
|
|
||
| test("ko: 'checked N ago' no longer doubles 전 when the time is 방금 전", () => { | ||
| const rendered = interpolate(ko["pws.dashboard.checkedAgo"], { time: ko["time.justNow"] }); | ||
| expect(rendered).not.toContain("전 전"); | ||
| expect(rendered).toBe("방금 전 확인"); | ||
| }); | ||
|
|
||
| test("Logs: no subtitle paragraph", () => { | ||
| expect(logs).not.toContain('{t("logs.subtitle")}'); | ||
| }); | ||
|
|
||
| test("Subagents: order hint is a focusable named tooltip; guidance/ultra sit behind a closed details", () => { | ||
| expect(subagents).not.toContain("swi-featured-hint"); | ||
| expect(subagents).toContain('<Tooltip content={<Trans k="sub.orderHint" cmd="spawn_agent" />}'); | ||
| expect(subagents).toContain('{t("sub.orderHintAria")}'); | ||
| const at = delegation.indexOf('<details className="swi-advanced">'); | ||
| expect(at).toBeGreaterThan(-1); | ||
| expect(delegation).not.toMatch(/<details className="swi-advanced"[^>]*\bopen\b/); | ||
| const inside = delegation.slice(at, delegation.indexOf("</details>", at)); | ||
| expect(inside).toContain('{t("dash.multiAgentGuidance")}'); | ||
| expect(inside).toContain('{t("sub.ultraMode")}'); | ||
| // The two daily decisions stay above the disclosure. | ||
| expect(delegation.indexOf('{t("sub.delegation.model")}')).toBeLessThan(at); | ||
| expect(delegation.indexOf('{t("dash.syncCodexSubagentDefaults")}')).toBeLessThan(at); | ||
| }); | ||
|
|
||
| test("Combos: search renders only when combos exist", () => { | ||
| expect(combos).toContain("{combos.length > 0 && (\n <div className=\"cwi-search-row\">"); | ||
| }); | ||
|
|
||
| test("Routing: dry-run only with a draft; analytics only with profiles", () => { | ||
| expect(routing).toContain("{draft && (\n <div className=\"panel\""); | ||
| expect(routing).toContain("{profiles.length > 0 && (\n <div className=\"panel\""); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Clear
querywhen the combo list becomes empty.At Line 113, the new condition hides the only input that can clear
query, but the state persists whencombosbecomes empty. If a user searches for one value, removes all combos, and creates a first combo that does not match that value,filteredremains empty and the rail showscws.noSearchResultseven though the new combo exists.Clear
querywhen the list reaches zero, or before saving the retained first-combo editor. Add a regression test for this sequence.Suggested fix
const [query, setQuery] = useState(""); + useEffect(() => { + if (combos.length === 0) setQuery(""); + }, [combos.length]);🤖 Prompt for AI Agents