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:00 and 06:00โ€“10:00 UTC).
  • ๐ŸŸก 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.

Screenshot placeholder: add a frame showing the dot next to the DeepSeek group in the model menu

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-harness checkout on 0.1.0-rc.7 (the patch baseline; other versions may need the manual fallback below), Node ^22.19 || >=24, and a working pnpm install (the web profile uses packages/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:

FileChange
packages/client/ui-model-selection/src/client/slots.tsnew conversation.input.model.providerBadge slot in SlotMap (list, session scope)
packages/client/ui-model-selection/src/client/index.tsdeclares the child slot on the model seat's register (children)
packages/client/ui-model-selection/src/client/ModelSelect.tsxrenders 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.tsxrenderSlot/SessionProvider stubs + badge test
packages/bundle/web-app/cordis.patch.ymlui-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.jsonproject 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

  1. Open a session and the composer's model selector.
  2. Open the model menu: the group whose name/id contains deepseek shows the dot; other groups show nothing.
  3. The trigger shows the dot when the current provider is DeepSeek.
  4. 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

  1. Remove the ui-rush-hour row from packages/bundle/web-app/cordis.patch.yml and the dependency from packages/bundle/web-app/package.json.
  2. rm -rf packages/client/ui-rush-hour.
  3. Revert the host patch: git apply -R <this-repo>/host-patch/host-changes.diff.
  4. pnpm install && pnpm run gen-client-catalog.
  5. Restart the server and reload.

Known limitations

  • Windows are fixed at the China preset. The client-plugin boot wire does not yet carry config from cordis.yml to the browser half, so the windows are a constant. The evaluation already accepts any RushInterval[]; 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:

  1. 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
    }
  }
}
  1. ui-model-selection/src/client/index.ts โ€” on the conversation.input.model seat's slots.register, add the children:
children: {
  'conversation.input.model.providerBadge': { kind: 'list', scope: 'session' },
},
  1. ui-model-selection/src/client/ModelSelect.tsx โ€” import PropsRenderSlots from @deepseek-ai/dsh-client-ui-slots, add & PropsRenderSlots<'conversation.input.model.providerBadge'> to the props, and destructure renderSlot/SessionProvider. Render the slot wrapped in SessionProvider in two places: next to modelLabel on the trigger (using the currentChoice group) and inside each group title in the menu (using group.id/group.name as the { provider, name } owner).

  2. packages/bundle/web-app/cordis.patch.yml โ€” inside the insert block, next to the ui-model-selection row, add:

    - id: ui-rush-hour
      name: '@deepseek-ai/dsh-client-ui-rush-hour'
  1. packages/bundle/web-app/package.json โ€” in dependencies:
"@deepseek-ai/dsh-client-ui-rush-hour": "workspace:^",
  1. tsconfig.client.json โ€” in references:
{ "path": "./packages/client/ui-rush-hour" },

Troubleshooting

  • Boot fails / error page โ€” the plugin bundle was not built. Run both bundle commands 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 run DSH_SNAPSHOT=refresh.

License

MIT