The source-level suite needs no lib/ artifacts, so PR checks test
first and build afterwards solely to feed scripts/bundle-smoke.mjs;
the release workflow gains the same smoke step between test and
publish.
Compress the CSS-channel note in tsdown.config.ts, the vitest lane
explainer and the release workflow header; fix the install-hooks header
(git only consults .git/hooks when core.hooksPath is unset); slim the
social-preview generator and bundle-patch comments.
* feat: add PR checks workflow for automated testing and validation
* feat(client): hide the composer while the Context tab is active
The view ring mounts one view at a time, so the tab's .lc-root existing
inside the shell scrollport is exactly 'Context tab active' — a single
:has() rule hides the sticky composer seat and hands its height to the
dashboard. display:none keeps the seat mounted, so drafts survive the
round trip.
Interactive takeovers (approval / question / plan-review panels) replace
the bar inside the same seat; keep the seat visible while one is present
so a pending decision is never hidden behind the focus mode.
Closes#11
* ci: build before test in PR checks
Client tests mount the built bundle (viewBed.ts reads lib/client.js), so
pnpm test without a prior build fails with ENOENT in a clean checkout.
One job instead of two: the dsh-plugin-checker step now runs right after
checkout inside publish, dropping the needs: handoff (saves a runner
spin-up per release; the check still gates every later step).
Mirror dsh's own lint setup (oxlint 1.76.0 pinned exact, oxlint-tsgolint
7.0.2001 with options.typeAware, correctness category off, hand-picked
rule list; jsPlugins: eslint-plugin-sonarjs + @stylistic/eslint-plugin
with the official style profile: indent 2, semi never, single quotes,
comma-dangle multiline, max-len 140). Scope mirrors official too: src TS
only, generated/bundled output (lib, *.js, *.mjs, *.config.ts) ignored.
Wiring: pnpm run lint / lint:fix scripts, plus a Lint step in the
release workflow between Build and Test.
Fixes applied (runtime behavior unchanged; verified against dsh
0.1.0-rc.7 and 0.1.1-rc.2 wire shapes, see below):
- autofix pass: arrow-parens, comma-dangle, eol-last, indent, quotes
- restrict-plus-operands: 31 string+number concatenations -> template
literals (restrict-template-expressions allows numbers)
- no-unused-vars: drop unused imports/destructured names
- no-unnecessary-type-conversion: drop redundant String() where the
field is already string-typed
- no-unsafe-*: type the fetch-json boundary in latestVersion, narrow
unknown before spread/cast instead of trusting any
- no-unnecessary-condition, three honest categories:
* boundary sanitizers (services.timelineOf/headersOf, RawBlocks,
headers.recordOf) now read through Record<string, unknown> instead
of casting first, so the defensive checks are type-meaningful again
* post-sanitize consumers (contextView/headline/assemble/browser)
drop the fallback-empty-array on collections the sanitizer
guarantees
* array-index-undefined gaps (fold turn attach, headers dedupe) use
.at() so the past-the-end guard is real to the type system
- no-non-null-assertion: stackedBar reserve carries the checked max
- no-floating-promises: void the fire-and-forget version check
- intentional-contract suppressions with rationale: react.ts keeps its
injected require (closure-factory loader contract)
- type-honest hardening kept deliberately: MessageSource.sections
entries stay nullable (foreign plugins author snapshot sources) and
isInjection still rejects a null source — foreign message payloads
must never crash the fold
Cross-version audit (0.1.0-rc.7 vs 0.1.1-rc.2, message parsing focus):
- core/session types.ts: only additive optional interrupted on
assistant/message (unread by us); every event payload we fold
(request/header, request/context, tool/call, tool/result,
user/message, assistant/message, compaction/*) is byte-identical
- llm types.ts / message.ts / content.ts block shapes: identical
- token-meter estimate.ts: identical, and official has NO null-entry
guards on content blocks — our removed guards match official trust,
our retained fallback-empty-string / typeof reads stay a leniency
superset
- deriveEventMessage (core/session surface.ts): identical
- ui-conversation chat-nodes contract: unchanged; our client reads are
minimally-typed unknowns by design
- tool/call callId is a branded string in both versions, so the
narrowed typeof guard drops only an impossible coercion