mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 08:52:21 +00:00
feat: move theme picker into mobile menu
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# Telemetry
|
||||
|
||||
ClawdHub uses **minimal telemetry** to compute **install counts** (what’s 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 aren’t 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 don’t 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
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user