dsh-rush-hour
August 19, 2026 ยท View on GitHub
A DeepSeek Harness web plugin that shows a rush-hour status dot beside DeepSeek providers in the model selector, evaluated strictly in UTC against AI usage peak windows. Concept from ericsantos/ai-rush-hour.
- ๐ด Peak (rush) โ the current UTC time is inside an AI usage peak window
(default "China" preset:
01:00โ04:00and06:00โ10:00UTC). - ๐ก Warning โ outside a window, but one opens within 30 minutes.
- ๐ข Off-peak โ everywhere else.
The dot appears in the chat's model selector: beside each provider name in
the menu's group titles and beside the current model on the composer
trigger (only when the current provider is DeepSeek). Providers whose route id
or display name do not match deepseek (case-insensitive) get no indicator.

How it works
The badge is a pure browser-side presentation. It evaluates the current UTC
time against the built-in rush windows every 30 seconds and colors a small dot
using the harness semantic state tokens (--dsw-alias-state-*). Nothing it
computes reaches a model request or the session log.
The plugin contributes one entry to the model seat's conversation.input.model.providerBadge
slot โ a new list-kind, session-scoped slot declared by ui-model-selection โ
so other provider-status plugins can share the same hole.
Repository layout
dsh-rush-hour/
โโโ README.md # this guide
โโโ LICENSE # MIT
โโโ package/ # the plugin package (copy into packages/client/)
โ โโโ package.json # @deepseek-ai/dsh-client-ui-rush-hour
โ โโโ tsconfig.json # extends tsconfig.base.client.json
โ โโโ tsdown.config.ts # client bundle (clientBundle)
โ โโโ src/
โ โ โโโ index.ts # node half (intentionally empty)
โ โ โโโ invariant.ts # invariant companion
โ โ โโโ client/ # browser half: rush.ts, RushBadge.tsx,
โ โ # index.ts (apply), locales.ts, slots.ts
โ โโโ tests/ # unit + component + wiring suites (100% coverage)
โโโ host-patch/
โโโ host-changes.diff # the host-side changes required by the plugin
The plugin does not work on its own: the badge renders inside the
conversation.input.model.providerBadge slot that must exist in the target
harness's ui-model-selection package. host-patch/host-changes.diff carries
exactly those changes (6 host files + 1 test file).
Installing in another DeepSeek Harness
Requirements: a
deepseek-harnesscheckout on0.1.0-rc.7(the patch baseline; other versions may need the manual fallback below), Node^22.19 || >=24, and a workingpnpm install(the web profile usespackages/bundle/web-app).
1. Copy the package
cd <YOUR_HARNESS>
cp -r <this-repo>/package packages/client/ui-rush-hour
2. Apply the host patch
From your checkout root:
git apply <this-repo>/host-patch/host-changes.diff
The patch only touches:
| File | Change |
|---|---|
packages/client/ui-model-selection/src/client/slots.ts | new conversation.input.model.providerBadge slot in SlotMap (list, session scope) |
packages/client/ui-model-selection/src/client/index.ts | declares the child slot on the model seat's register (children) |
packages/client/ui-model-selection/src/client/ModelSelect.tsx | renders the slot beside the current model (trigger) and the menu group titles, wrapped in SessionProvider |
packages/client/ui-model-selection/tests/model-select.client.spec.tsx | renderSlot/SessionProvider stubs + badge test |
packages/bundle/web-app/cordis.patch.yml | ui-rush-hour row in the web profile's dsh.client roster |
packages/bundle/web-app/package.json | @deepseek-ai/dsh-client-ui-rush-hour dependency |
tsconfig.client.json | project reference in the client aggregate |
3. Install dependencies
pnpm install
4. Build the client bundles
pnpm --filter @deepseek-ai/dsh-client-ui-rush-hour bundle
pnpm --filter @deepseek-ai/dsh-client-ui-model-selection bundle
Without these, the server answers 404 on
/plugins/@deepseek-ai/dsh-client-ui-rush-hour/client.js and the boot fails loud.
5. Regenerate the client catalog (doc-sync/CI gates)
pnpm run gen-client-catalog
Only strictly needed if your repo runs doc-sync/verify-client-catalog.
6. Rebuild and restart the web server
pnpm run build:web
Composition changes only take effect after a server restart (the client
plugin table is scanned at boot). If you run the GUI with
pnpm dsh web --port 8000 --trusted-host <HOST>, stop it and start it again
with the same command, then hard-reload the page.
7. Verify
- Open a session and the composer's model selector.
- Open the model menu: the group whose name/id contains
deepseekshows the dot; other groups show nothing. - The trigger shows the dot when the current provider is DeepSeek.
- Check the state via the dot's
aria-label/title(green off-peak, amber within 30 minutes of a window, red inside a peak window).
Quick HTTP check (the boot graph must contain the row and the bundle must answer 200):
curl -s http://127.0.0.1:8000/ | grep -c rush-hour # expected 1
curl -s -o /dev/null -w '%{http_code}\n' \
http://127.0.0.1:8000/plugins/@deepseek-ai/dsh-client-ui-rush-hour/client.js # 200
Tests
pnpm run test:gui # client + host suites
pnpm exec vitest run packages/client/ui-rush-hour packages/client/ui-model-selection
Both packages carry 100% coverage (statements/branches/functions/lines). If
your CI runs the web snapshots (test:web), this change alters visible output
(the badge on the trigger) โ refresh the goldens intentionally:
DSH_SNAPSHOT=refresh pnpm run test:web
The patch also updates apps/web/tests/scaffold.ts normalizeAria with a
{{rush}} token so the phase label (which moves with the wall clock) does not
make goldens time-sensitive.
Uninstalling
- Remove the
ui-rush-hourrow frompackages/bundle/web-app/cordis.patch.ymland the dependency frompackages/bundle/web-app/package.json. rm -rf packages/client/ui-rush-hour.- Revert the host patch:
git apply -R <this-repo>/host-patch/host-changes.diff. pnpm install && pnpm run gen-client-catalog.- Restart the server and reload.
Known limitations
- Windows are fixed at the China preset. The client-plugin boot wire does
not yet carry
configfromcordis.ymlto the browser half, so the windows are a constant. The evaluation already accepts anyRushInterval[]; only the configuration surface is missing (us/custom intervals land when the config wire exists). - The badge exists only inside the model selector. Provider names on other surfaces (for example Settings โ Models) do not host the slot yet; adding it there means declaring the same hole on that screen.
- No backend or state: the plugin is pure client (wall clock + windows).
If the patch fails
host-changes.diff was generated against 0.1.0-rc.7. If git apply rejects,
recreate the changes manually:
ui-model-selection/src/client/slots.tsโ append:
export interface ProviderBadgeOwnerProps {
provider: string
name: string
}
declare module '@deepseek-ai/dsh-client-ui-slots' {
interface SlotMap {
'conversation.input.model.providerBadge': {
kind: 'list'
scope: 'session'
owner: ProviderBadgeOwnerProps
}
}
}
ui-model-selection/src/client/index.tsโ on theconversation.input.modelseat'sslots.register, add the children:
children: {
'conversation.input.model.providerBadge': { kind: 'list', scope: 'session' },
},
-
ui-model-selection/src/client/ModelSelect.tsxโ importPropsRenderSlotsfrom@deepseek-ai/dsh-client-ui-slots, add& PropsRenderSlots<'conversation.input.model.providerBadge'>to the props, and destructurerenderSlot/SessionProvider. Render the slot wrapped inSessionProviderin two places: next tomodelLabelon the trigger (using thecurrentChoicegroup) and inside each group title in the menu (usinggroup.id/group.nameas the{ provider, name }owner). -
packages/bundle/web-app/cordis.patch.ymlโ inside theinsertblock, next to theui-model-selectionrow, add:
- id: ui-rush-hour
name: '@deepseek-ai/dsh-client-ui-rush-hour'
packages/bundle/web-app/package.jsonโ independencies:
"@deepseek-ai/dsh-client-ui-rush-hour": "workspace:^",
tsconfig.client.jsonโ inreferences:
{ "path": "./packages/client/ui-rush-hour" },
Troubleshooting
- Boot fails / error page โ the plugin bundle was not built. Run both
bundlecommands from step 4 and restart the server. - Badge does not appear โ confirm the boot graph contains the row
(
curl ... | grep rush-hour); that the current session has a DeepSeek provider/model selected; and that you are looking at the model selector (not Settings โ Models, which does not host the slot yet). - Web snapshots break at different times โ apply the
{{rush}}normalize (in the patch) and runDSH_SNAPSHOT=refresh.