Watch
0
0
Fork
You've already forked hyperhive
0

settings: move the theme/motion panel into @hive/shared, add motion to agent

Review feedback on this PR (mara): "i think the component should be
shared. motion setting is missing." Both addressed:

- `settings-storage.ts` (generic localStorage hook), `theme-apply.ts`,
  `motion-apply.ts`, and `SettingsMenu.tsx`/`.css` all move from
  swarm-ui's `lib/`/`shell/` into `@hive/shared/src/settings/` —
  agent's previous local copies are deleted outright rather than kept
  as a second implementation. One component, `Badge` trigger
  everywhere (already used elsewhere in swarm-ui, so not a new visual
  language there either) — storage keys stay caller-owned (`themeKey`/
  `motionKey` props + matching `useApplyThemeOverride`/
  `useApplyMotionOverride` calls at each package's single mount point)
  so agent and swarm-ui keep fully independent, non-colliding
  persisted settings.
- Agent's settings menu now includes the motion row, matching
  swarm-ui's. No animation in the agent package is gated behind
  `data-motion` yet — same as when swarm-ui first built this plumbing
  ahead of having a consumer — so it's currently inert there, ready for
  whenever agent grows a motion-guarded animation.
- swarm-ui's own theme default flips to `'dark'` as part of this move
  (`theme-apply.ts`'s new default), superseding PR #3715 — that PR
  becomes redundant once this lands and will be closed rather than
  merged, to avoid the two colliding on the same file.

Verified end-to-end with real screenshots on both pages: shared
component renders identically (Badge trigger, theme+motion rows, dark
default) on agent's mock server and a static rebuild of swarm-ui's
dist.
This commit is contained in:
iris 2026-08-29 11:11:39 +02:00
commit 9720bdfad0
16 changed files with 230 additions and 487 deletions

View file

@ -30,12 +30,20 @@ import { useEffect, useRef, useState } from 'preact/hooks';
import type { ComponentChildren } from 'preact';
import { Link, useLocation } from 'wouter-preact';
import { LinksMenu } from './LinksMenu.js';
import { SettingsMenu } from './SettingsMenu.js';
import { UserMenu } from './UserMenu.js';
import { useApplyThemeOverride } from '../lib/theme-apply.js';
import { useApplyMotionOverride } from '../lib/motion-apply.js';
import { SettingsMenu } from '@hive/shared/settings-menu.js';
import { useApplyThemeOverride } from '@hive/shared/theme-apply.js';
import { useApplyMotionOverride } from '@hive/shared/motion-apply.js';
import './Shell.css';
// Storage keys this app owns for `@hive/shared`'s settings mechanism —
// kept here, not derived, so the two mount points below (the
// `useApply*` effects and `<SettingsMenu>`) always agree on which
// browser-local key they're reading/writing. Theme defaults to `'dark'`,
// not `'system'` — see `@hive/shared/theme-apply.js`'s file comment.
const THEME_KEY = 'swarm-ui:theme-override';
const MOTION_KEY = 'swarm-ui:motion-override';
const NAV_ITEMS: { href: string; label: string; accent: string }[] = [
{ href: '/', label: 'hives', accent: 'var(--purple)' },
{ href: '/agents', label: 'agents', accent: 'var(--green)' },
@ -68,7 +76,7 @@ const HOP_MS = 140;
// multi-step `setTimeout` sequence has no CSS equivalent to hook, so it
// checks the same two sources directly. `data-motion` takes precedence
// over the OS preference either direction (reduce *or* allow), same
// override semantics `lib/motion-apply.ts` establishes.
// override semantics `@hive/shared/motion-apply.js` establishes.
function prefersReducedMotion(): boolean {
const override = document.documentElement.dataset.motion;
if (override === 'reduce') return true;
@ -127,11 +135,12 @@ export function Shell({ children }: { children: ComponentChildren }) {
// Applied once here, not inside `SettingsMenu` — every route mounts
// through this one `<Shell>`, so the override takes effect regardless
// of which page is showing or whether the menu's ever been opened,
// and `useLocalSetting`'s same-tab subscription (settings-storage.ts)
// means `SettingsMenu` changing the stored value re-runs these
// effects without either component needing a reference to the other.
useApplyThemeOverride();
useApplyMotionOverride();
// and `useLocalSetting`'s same-tab subscription
// (`@hive/shared/settings-storage.js`) means `SettingsMenu` changing
// the stored value re-runs these effects without either component
// needing a reference to the other.
useApplyThemeOverride(THEME_KEY);
useApplyMotionOverride(MOTION_KEY);
// Measures nav item `index`'s rect relative to `.shell-nav`.
// `getBoundingClientRect()` on both elements at the same tick keeps
@ -282,7 +291,7 @@ export function Shell({ children }: { children: ComponentChildren }) {
`margin-left: auto` split the available space between them
instead of sitting flush together at the right edge. */}
<div class="shell-header-actions">
<SettingsMenu />
<SettingsMenu themeKey={THEME_KEY} motionKey={MOTION_KEY} />
<LinksMenu />
<UserMenu />
</div>