feat: move theme picker into mobile menu

This commit is contained in:
Peter Steinberger
2026-01-06 02:03:43 +01:00
parent 860b3a369c
commit dfe6150474
5 changed files with 157 additions and 32 deletions
+2
View File
@@ -6,9 +6,11 @@
- Telemetry: track installs via `clawdhub sync` (logged-in only), per root, with 120-day staleness.
- Skills: show current + all-time installs; sort by installs.
- Profile: private "Installed" tab with JSON export + delete telemetry controls.
- Docs: add `docs/telemetry.md` (what we track + how to opt out).
### Changed
- CLI: telemetry opt-out via `CLAWDHUB_DISABLE_TELEMETRY=1`.
- Web: move theme picker into mobile menu.
## 0.0.4 - 2026-01-05
+11
View File
@@ -25,6 +25,17 @@ Live: `https://clawdhub.com`
- Search: OpenAI embeddings (`text-embedding-3-small`) + Convex vector search.
- API schema + routes: `packages/schema` (`clawdhub-schema`).
## Telemetry
ClawdHub tracks minimal **install telemetry** (to compute install counts) when you run `clawdhub sync` while logged in.
Disable via:
```bash
export CLAWDHUB_DISABLE_TELEMETRY=1
```
Details: `docs/telemetry.md`.
## Repo layout
- `src/` — TanStack Start app (routes, components, styles).
+85
View File
@@ -0,0 +1,85 @@
# Telemetry
ClawdHub uses **minimal telemetry** to compute **install counts** (whats actually in use) and to power better sorting/filtering.
This is based on the CLI `clawdhub sync` command.
## When telemetry is collected
Telemetry is only sent when:
- You are **logged in** in the CLI (we already require auth for sync/publish flows).
- You run `clawdhub sync`.
- Telemetry is **not disabled** (see “How to disable” below).
If you are not logged in, nothing is reported.
## What we collect
On each `clawdhub sync`, the CLI reports a **full snapshot** of what it found, grouped by scan root (“folder/root”).
For each root we store:
- `rootId`: a **SHA-256 hash** of the canonical root path (server never sees the raw path).
- `label`: a human-readable label derived from the last two path segments (home paths are shown with `~`).
- `firstSeenAt`, `lastSeenAt`, optional `expiredAt`.
For each skill found under a root we store:
- `skillId` (resolved by slug; only skills that exist in the registry are tracked).
- `firstSeenAt`, `lastSeenAt`.
- `lastVersion` (best-effort; currently the registry-matched version if known).
- optional `removedAt` when a previously-reported install disappears from a root.
### What we do *not* collect
- No raw absolute folder paths (only hashed `rootId` + a short display label).
- No file contents.
- No per-run logs, prompts, or other CLI output.
- No tracking for skills that arent uploaded to the registry (unknown slugs are ignored).
## Install counts
We maintain two counters per skill:
- `installsCurrent`: unique users who currently have the skill installed in at least one active root.
- `installsAllTime`: unique users who have ever reported the skill installed.
### Multiple roots
If you sync from multiple folders, we treat each scan root independently. A skill is “currently installed” if it exists in **any** active root.
### Uninstall detection
Because `sync` reports the full set per root:
- If a skill disappears from a root on the next sync, we mark it removed for that root.
- If the skill is removed from all of your roots, it no longer counts toward `installsCurrent`.
- `installsAllTime` never decreases unless you delete telemetry (see below).
### Staleness (120 days)
Roots that dont report telemetry for **120 days** are marked stale and their installs stop counting toward `installsCurrent`.
This is evaluated lazily (on the next telemetry report) to avoid background jobs.
## Transparency + user controls
ClawdHub provides a private “Installed” tab on your own profile:
- Shows the exact roots + installed skills we store.
- Includes a **JSON export** view.
- Includes a **Delete telemetry** action to remove all stored telemetry for your account.
Everyone else only sees **aggregated install counters**; no one else can see your roots/folders.
Deleting your account also deletes your telemetry data.
## How to disable telemetry
Set the environment variable:
```bash
export CLAWDHUB_DISABLE_TELEMETRY=1
```
With this set, the CLI will not send telemetry during `clawdhub sync`.
+51 -32
View File
@@ -28,6 +28,19 @@ export default function Header() {
const initial = (me?.displayName ?? me?.name ?? handle).charAt(0).toUpperCase()
const isModerator = me?.role === 'admin' || me?.role === 'moderator'
const setTheme = (next: 'system' | 'light' | 'dark') => {
startThemeTransition({
nextTheme: next,
currentTheme: mode,
setTheme: (value) => {
const nextMode = value as 'system' | 'light' | 'dark'
applyTheme(nextMode)
setMode(nextMode)
},
context: { element: toggleRef.current },
})
}
return (
<header className="navbar">
<div className="navbar-inner">
@@ -98,41 +111,47 @@ export default function Header() {
<Link to="/admin">Admin</Link>
</DropdownMenuItem>
) : null}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => setTheme('system')}>
<Monitor className="h-4 w-4" aria-hidden="true" />
System
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('light')}>
<Sun className="h-4 w-4" aria-hidden="true" />
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
<Moon className="h-4 w-4" aria-hidden="true" />
Dark
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<ToggleGroup
ref={toggleRef}
type="single"
value={mode}
onValueChange={(value) => {
if (!value) return
startThemeTransition({
nextTheme: value as 'system' | 'light' | 'dark',
currentTheme: mode,
setTheme: (next) => {
const nextMode = next as 'system' | 'light' | 'dark'
applyTheme(nextMode)
setMode(nextMode)
},
context: { element: toggleRef.current },
})
}}
aria-label="Theme mode"
>
<ToggleGroupItem value="system" aria-label="System theme">
<Monitor className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">System</span>
</ToggleGroupItem>
<ToggleGroupItem value="light" aria-label="Light theme">
<Sun className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">Light</span>
</ToggleGroupItem>
<ToggleGroupItem value="dark" aria-label="Dark theme">
<Moon className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">Dark</span>
</ToggleGroupItem>
</ToggleGroup>
<div className="theme-toggle">
<ToggleGroup
ref={toggleRef}
type="single"
value={mode}
onValueChange={(value) => {
if (!value) return
setTheme(value as 'system' | 'light' | 'dark')
}}
aria-label="Theme mode"
>
<ToggleGroupItem value="system" aria-label="System theme">
<Monitor className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">System</span>
</ToggleGroupItem>
<ToggleGroupItem value="light" aria-label="Light theme">
<Sun className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">Light</span>
</ToggleGroupItem>
<ToggleGroupItem value="dark" aria-label="Dark theme">
<Moon className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">Dark</span>
</ToggleGroupItem>
</ToggleGroup>
</div>
{isAuthenticated && me ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
+8
View File
@@ -227,6 +227,10 @@ code {
align-items: center;
}
.theme-toggle {
display: inline-flex;
}
.user-trigger {
display: inline-flex;
align-items: center;
@@ -1232,6 +1236,10 @@ code {
display: inline-flex;
}
.theme-toggle {
display: none;
}
.hero {
padding: 64px 18px 46px;
}