Skip to content

fix: UI polish, auth flow, and webkit compatibility#1556

Open
ImLukeF wants to merge 6 commits intomainfrom
imlukef/ui-auth-webkit-fixes
Open

fix: UI polish, auth flow, and webkit compatibility#1556
ImLukeF wants to merge 6 commits intomainfrom
imlukef/ui-auth-webkit-fixes

Conversation

@ImLukeF
Copy link
Copy Markdown
Contributor

@ImLukeF ImLukeF commented Apr 6, 2026

Summary

  • Squircle border-radius on skills toolbar filter chips and view toggle
  • Category dropdown with icons (MCP Tools, Prompts, Workflows, Dev Tools, Data & APIs, Security, Automation, Other)
  • Skills count updates when filtering (Staff Picks, Clean only)
  • Keep stale results visible during filter changes (no blank page flash)
  • Auth flow improvements and webkit compatibility fixes
  • Sign-in button extracted to reusable component
  • Test coverage for settings route and skill detail

Test plan

  • Visual check: /skills filter chips, category dropdown, sort dropdown all match
  • Toggle Staff Picks / Clean only — count updates, no page flash
  • Category dropdown filters skills correctly
  • Auth flow works on Safari/webkit

Copilot AI review requested due to automatic review settings April 6, 2026 11:20
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clawhub Ready Ready Preview Apr 6, 2026 11:20am

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR polishes the skills browsing UI (filters, counts, category dropdown), improves Safari/WebKit compatibility, and refactors the sign-in UX into a reusable component used across multiple routes.

Changes:

  • Added a reusable SignInButton and updated multiple signed-out empty states to use it.
  • Introduced skill categories (with icons) and updated skills browsing/count behavior (including “Other” category logic).
  • Added WebKit/Safari compatibility patches (including a Vite transform for Ark/Arktype in-operator usage) and expanded test coverage.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
vite.config.ts Adds a Vite pre-transform plugin to patch Ark/Arktype code for Safari/WebKit compatibility.
src/routes/u/$handle.tsx Uses the shared SignInButton inside a signed-out empty state.
src/routes/stars.tsx Uses the shared SignInButton inside a signed-out empty state.
src/routes/skills/index.tsx Updates the displayed counts to reflect filtering and reduce “blank page” flashes.
src/routes/skills/-useSkillsBrowseModel.ts Adds “Other” category filtering and category keyword support for browsing logic.
src/routes/skills/-SkillsToolbar.tsx Adds category dropdown with icons; shows an active capability-tag chip.
src/routes/settings.tsx Uses the shared SignInButton in the signed-out settings view.
src/routes/publish-skill.tsx Replaces inline auth actions with SignInButton; improves scrollIntoView guard.
src/routes/import.tsx Uses the shared SignInButton in the signed-out import view.
src/routes/dashboard.tsx Uses the shared SignInButton in the signed-out dashboard view.
src/routes/cli/auth.tsx Refactors CLI auth sign-in to use SignInButton and removes duplicated redirect logic.
src/routes/-settings.test.tsx Updates settings tests to mock auth actions for SignInButton usage.
src/lib/packageApi.ts Replaces in checks with hasOwnProperty for WebKit-safe property guards.
src/lib/hasOwnProperty.ts Introduces a shared hasOwnProperty type-guard helper.
src/lib/convexError.ts Uses hasOwnProperty for safer error shape inspection.
src/lib/categories.ts Introduces skill category definitions and flattened keyword list.
src/components/UserBadge.tsx Uses hasOwnProperty for safer type narrowing on user-like objects.
src/components/SkillDetailPage.tsx Uses hasOwnProperty for safer error formatting.
src/components/SignInButton.tsx Adds reusable sign-in button with shared error handling + redirect behavior.
src/components/SignInButton.test.tsx Adds unit tests covering default redirect and error-handling behavior.
src/components/Header.tsx Switches header sign-in to SignInButton and removes duplicated auth error logic.
src/tests/skills-index.test.tsx Adds test ensuring capability-tag chip is shown and can be cleared.
src/tests/skill-detail-page.test.tsx Adds a mock to stabilize skill detail page tests with new dependencies.

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

Comment on lines +59 to +68
const CATEGORY_ICONS: Record<string, React.ReactNode> = {
"mcp-tools": <Plug size={13} />,
prompts: <MessageSquare size={13} />,
workflows: <GitBranch size={13} />,
"dev-tools": <Wrench size={13} />,
data: <Database size={13} />,
security: <Shield size={13} />,
automation: <Zap size={13} />,
other: <Package size={13} />,
};
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

React.ReactNode is used for CATEGORY_ICONS and FilterChip props, but React isn’t imported in this file. With the automatic JSX runtime, JSX works, but the React namespace for types will be undefined and TypeScript will fail to compile. Import type React from "react" or switch these annotations to ReactNode via import type { ReactNode } from "react".

Copilot uses AI. Check for mistakes.
</span>
)}
<span className="ml-2 text-lg font-normal text-[color:var(--ink-soft)] opacity-70">
({model.hasQuery || model.highlightedOnly || model.nonSuspiciousOnly
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The header count switches to model.sorted.length only for query/highlighted/nonSuspicious filters, but not when capabilityTag is active. If a capability tag filter is set, the count in the title will still show the total skills rather than the filtered count. Include model.capabilityTag in this condition so the displayed count matches the active filters.

Suggested change
({model.hasQuery || model.highlightedOnly || model.nonSuspiciousOnly
({model.hasQuery || model.highlightedOnly || model.nonSuspiciousOnly || model.capabilityTag

Copilot uses AI. Check for mistakes.
Comment on lines +318 to 322
const browseResponse = response as PackageCatalogBrowseResponse;
return {
items:
"results" in response ? response.results.map((entry) => entry.package) : response.items,
nextCursor: "results" in response ? null : response.nextCursor,
items: browseResponse.items,
nextCursor: browseResponse.nextCursor,
};
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

response is cast to PackageCatalogBrowseResponse without validating that items is present/array-shaped. If the API ever returns an unexpected shape (or results exists but isn’t an array), this will return undefined fields and likely fail later. Consider using hasOwnProperty(response, "items") + Array.isArray(...) (and validating nextCursor) and throwing a PackageApiError (or a clear fallback) when the shape is unrecognized.

Copilot uses AI. Check for mistakes.
Comment on lines +145 to +151
for (const [from, to] of fix.replacements) {
if (!nextCode.includes(from)) {
this.error(`Expected to patch ${from} in ${normalizedId}`);
}
nextCode = nextCode.replaceAll(from, to);
}

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

This Vite transform hard-fails the build via this.error(...) if any expected source substring is missing. Since this is patching code in dependencies, a minor upstream version change or different bundling output can break builds unexpectedly. Consider making the patch more resilient (e.g., tolerate missing patterns with a warning, or gate the patch to a known dependency version / specific build mode).

Suggested change
for (const [from, to] of fix.replacements) {
if (!nextCode.includes(from)) {
this.error(`Expected to patch ${from} in ${normalizedId}`);
}
nextCode = nextCode.replaceAll(from, to);
}
let patchedAny = false;
const missingPatterns: string[] = [];
for (const [from, to] of fix.replacements) {
if (!nextCode.includes(from)) {
missingPatterns.push(from);
continue;
}
nextCode = nextCode.replaceAll(from, to);
patchedAny = true;
}
if (missingPatterns.length > 0) {
this.warn(
`Skipped ${missingPatterns.length} ark safari patch replacement(s) in ${normalizedId}: ${missingPatterns.join(", ")}`,
);
}
if (!patchedAny) return null;

Copilot uses AI. Check for mistakes.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 6, 2026

Greptile Summary

This PR delivers UI polish to the skills browser (squircle filter chips, category dropdown with icons, per-filter skill-count updates), extracts SignInButton into a reusable component to consolidate the GitHub sign-in flow, and adds Safari/WebKit compatibility via a safari15 build target and a new patchArkSafariInOperator Rollup plugin in vite.config.ts.

  • SignInButton centralises OAuth initiation and error handling; Header, CliAuth, and Settings all now consume it, with the signingIn return-value pattern correctly handled and tested.
  • The category dropdown replaces the old capability-tag <Select>; selecting a category writes its first keyword to the q URL param so results flow through the existing text-search pipeline. The "Other" category is special-cased to __other__ and filtered client-side.
  • src/lib/categories.ts defines eight categories plus ALL_CATEGORY_KEYWORDS, used for the "Other" client-side filter in useSkillsBrowseModel.
  • The patchArkSafariInOperator plugin replaces problematic in-operator patterns in arktype with Reflect.has() calls; pairing this with build.target: 'safari15' addresses the webkit compatibility issues described in the PR.
  • The header skill count is updated to show a filtered value when Staff Picks or Clean only is active, but the guard condition does not cover the __other__ category case, causing it to show the unfiltered total when "Other" is selected.

Confidence Score: 4/5

Safe to merge with minor issues — one edge-case count bug for the "Other" category and a CSS dark-mode regression on toolbar elements.

Changes are well-scoped with good test coverage added for SignInButton, the settings route, and skill-detail. The webkit compatibility work is thorough and intentional. The two flagged issues are narrow in scope and do not affect core functionality.

src/routes/skills/index.tsx (count guard misses the isOtherCategory case), src/routes/skills/-SkillsToolbar.tsx (hardcoded rgba values break dark mode)

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/routes/skills/index.tsx
Line: 82-84

Comment:
**Header count shows total when "Other" category is active**

When the "Other" category is selected from the dropdown, `query === "__other__"` causes `isOtherCategory = true` and `hasQuery = false` in the model (by design). Since neither `highlightedOnly` nor `nonSuspiciousOnly` are set, the header count falls through to `totalSkillsText` — the unfiltered total — even though `model.sorted` is already client-side filtered to only "Other" items.

Every other active filter (Staff Picks, Clean only, any keyword category) correctly shows `model.sorted.length`. Only the "Other" branch is missed.

```suggestion
                ({model.hasQuery || model.highlightedOnly || model.nonSuspiciousOnly || model.query === "__other__"
                  ? model.sorted.length.toLocaleString("en-US")
                  : totalSkillsText ?? "…"})
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/routes/skills/-SkillsToolbar.tsx
Line: 205

Comment:
**Hardcoded rgba values break dark mode**

The view-toggle container (line 205) and the `FilterChip` base style (line 252) both use hardcoded light-mode values:
- `bg-[rgba(255,255,255,0.94)]` — near-opaque white
- `border-[rgba(29,59,78,0.22)]` — fixed dark-ink opacity

These replaced the previous `var(--surface)` / `var(--line)` CSS custom properties that adapt to the active theme. In dark mode the chips and toggle will render with a bright white background instead of the expected dark surface color.

Consider restoring the CSS variable equivalents or adding `dark:` Tailwind variants so the toolbar respects the user's theme.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: recompute other skills category fil..." | Re-trigger Greptile

Comment on lines +82 to +84
({model.hasQuery || model.highlightedOnly || model.nonSuspiciousOnly
? model.sorted.length.toLocaleString("en-US")
: totalSkillsText ?? "…"})
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.

P1 Header count shows total when "Other" category is active

When the "Other" category is selected from the dropdown, query === "__other__" causes isOtherCategory = true and hasQuery = false in the model (by design). Since neither highlightedOnly nor nonSuspiciousOnly are set, the header count falls through to totalSkillsText — the unfiltered total — even though model.sorted is already client-side filtered to only "Other" items.

Every other active filter (Staff Picks, Clean only, any keyword category) correctly shows model.sorted.length. Only the "Other" branch is missed.

Suggested change
({model.hasQuery || model.highlightedOnly || model.nonSuspiciousOnly
? model.sorted.length.toLocaleString("en-US")
: totalSkillsText ?? "…"})
({model.hasQuery || model.highlightedOnly || model.nonSuspiciousOnly || model.query === "__other__"
? model.sorted.length.toLocaleString("en-US")
: totalSkillsText ?? "…"})
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/skills/index.tsx
Line: 82-84

Comment:
**Header count shows total when "Other" category is active**

When the "Other" category is selected from the dropdown, `query === "__other__"` causes `isOtherCategory = true` and `hasQuery = false` in the model (by design). Since neither `highlightedOnly` nor `nonSuspiciousOnly` are set, the header count falls through to `totalSkillsText` — the unfiltered total — even though `model.sorted` is already client-side filtered to only "Other" items.

Every other active filter (Staff Picks, Clean only, any keyword category) correctly shows `model.sorted.length`. Only the "Other" branch is missed.

```suggestion
                ({model.hasQuery || model.highlightedOnly || model.nonSuspiciousOnly || model.query === "__other__"
                  ? model.sorted.length.toLocaleString("en-US")
                  : totalSkillsText ?? "…"})
```

How can I resolve this? If you propose a fix, please make it concise.


{/* View toggle */}
<div className="inline-flex items-center rounded-[var(--radius-pill)] border border-[color:var(--line)] bg-[color:var(--surface)] p-0.5">
<div className="inline-flex items-center rounded-[var(--radius-sm)] border border-[rgba(29,59,78,0.22)] bg-[rgba(255,255,255,0.94)] p-0.5">
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.

P2 Hardcoded rgba values break dark mode

The view-toggle container (line 205) and the FilterChip base style (line 252) both use hardcoded light-mode values:

  • bg-[rgba(255,255,255,0.94)] — near-opaque white
  • border-[rgba(29,59,78,0.22)] — fixed dark-ink opacity

These replaced the previous var(--surface) / var(--line) CSS custom properties that adapt to the active theme. In dark mode the chips and toggle will render with a bright white background instead of the expected dark surface color.

Consider restoring the CSS variable equivalents or adding dark: Tailwind variants so the toolbar respects the user's theme.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/skills/-SkillsToolbar.tsx
Line: 205

Comment:
**Hardcoded rgba values break dark mode**

The view-toggle container (line 205) and the `FilterChip` base style (line 252) both use hardcoded light-mode values:
- `bg-[rgba(255,255,255,0.94)]` — near-opaque white
- `border-[rgba(29,59,78,0.22)]` — fixed dark-ink opacity

These replaced the previous `var(--surface)` / `var(--line)` CSS custom properties that adapt to the active theme. In dark mode the chips and toggle will render with a bright white background instead of the expected dark surface color.

Consider restoring the CSS variable equivalents or adding `dark:` Tailwind variants so the toolbar respects the user's theme.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d96cddfdbc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +99 to +101
} else if (cat.slug === "other") {
onQueryChange("__other__");
} else if (cat.keywords[0]) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid persisting internal "other" token as user query

When the user picks the Other category, this code writes "__other__" into the shared query state, and that state is also bound directly to the search input. In practice, users will see the raw sentinel in the text box and URL (for example /skills?q=__other__), which is an internal implementation detail and creates confusing UI/state behavior around search and filtering. Keep category selection separate from the free-text query (or map this sentinel back to an empty/display-safe query value before rendering).

Useful? React with 👍 / 👎.

</span>
)}
<span className="ml-2 text-lg font-normal text-[color:var(--ink-soft)] opacity-70">
({model.hasQuery || model.highlightedOnly || model.nonSuspiciousOnly
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.

When the "Other" category is selected, the header displays total skills count instead of the filtered count

Fix on Vercel

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.

2 participants