v3.6.0 into main - #113
Merged
Merged
Conversation
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](actions/cache@v4...v5) --- updated-dependencies: - dependency-name: actions/cache dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@v4...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Convert global mixins to composables
Replace the two global Options API mixins with Vue 3 composables under a
new src/composables/ directory:
- useDownload() -> { downloadStager, downloadText } (stateless DOM/blob
helpers, logic relocated from the download-stager mixin)
- useCopyStager() -> { copyStager }, injecting "snack" internally and
delegating to copyToClipboard() exactly as before
Migrate all 8 consumers off the mixins via a minimal setup() bridge that
re-exposes the same method names the templates already use, leaving the
Options API components otherwise unchanged. Delete the now-empty
src/mixins/ directory.
Add an .eslintrc.js override disabling import/prefer-default-export for
src/composables/** so composables can follow the idiomatic named-export
(useFoo) convention.
Tighten JSDoc on the composables ({string} params, document the
empty/trailing-slash no-op in downloadStager) and drop a redundant
always-true guard in downloadStager.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add CHANGELOG entry for mixin-to-composable conversion
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Replace moment with dayjs
moment is deprecated. dayjs is a near drop-in replacement with identical
format tokens, so every existing format string is unchanged byte-for-byte.
- Add src/plugins/dayjs.js: a central dayjs instance with the relativeTime
plugin extended (required for .fromNow()). All consumers import the
configured instance from "@/plugins/dayjs".
- Swap moment for dayjs in DateTimeDisplay, ClickToEdit, AgentJobs, and
AgentForm (import, data() registration, and call sites in script +
template). Format strings and fromNow() behavior are unchanged.
- package.json: drop "moment", add "dayjs" ^1.11.20.
Verified: dayjs fromNow() phrasing matches moment's ("a few seconds ago",
"2 minutes ago", "in 5 minutes"); all format outputs identical. Lint clean
(0 errors), prettier clean, yarn build succeeds, and the agent-detail /
agent-jobs / agents-list e2e specs pass (6/6).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Drop unused dayjs registration from ClickToEdit data()
ClickToEdit registered dayjs in data() but never used it: the template
references no date helper, and the only call site (onDatePicked) uses the
module-level imported dayjs, not this.dayjs. This was dead with moment too
and got faithfully ported in the migration; remove it now. Behavior-neutral.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Guard fromNow()/format() against invalid timestamps
Round-2 review caught a behavioral divergence: moment(null) / moment("")
rendered "Invalid date", but dayjs treats those as valid and emits a
confidently-wrong relative time ("a month ago"). Reachable via the
checkin_time/lastseen_time fields in AgentForm and the timestamp prop in
DateTimeDisplay (used across ~9 tables) if the backend ever sends null/"".
Move the inline template dayjs() expressions into a computed (DateTimeDisplay)
and a method (AgentForm) that short-circuit on dayjs().isValid(), rendering
the existing "N/A" placeholder (matches AgentJobs.formatDate) instead of a
bogus value. dayjs(undefined) stays valid (= now), so fields the backend
omits render exactly as before — identical to moment, and e2e stays green.
Moving the expressions out of the templates also makes the dayjs data()
registrations dead, so drop them (same cleanup as the ClickToEdit commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add CHANGELOG entry for moment-to-dayjs migration
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…table keys) (#283) * Remove dead vue.config.js Leftover vue-cli config that Vite ignores. Nothing in the repo imports or references it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add jsconfig.json for "@/" alias resolution Lets editors resolve the "@/*" -> "./src/*" alias used throughout the codebase. Editor-only metadata; does not affect the Vite build or ESLint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Declare Node >=20 in package.json engines CI already pins Node 20; declare it explicitly so local environments and tooling match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Remove leftover debug logging Drop console.log/console.warn debug noise in: - stores/listener-module.js (autorun save confirmation) - api/agent-api.js (killAgent args) - api/listener-api.js (raw error before reject) - api/download-api.js (response headers / filename) Error handling (handleError + reject, console.error) is left intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Remove unused uuid dependency uuid had zero references anywhere in src/ — it was a dead dependency. Drop it from package.json and the lockfile rather than upgrading it. No transitive dependency requires it. If random ids are needed later, the built-in crypto.randomUUID() (Web Crypto / Node >=20) covers the v4 case without a dependency. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Declare emits on components that were missing it Add explicit emits: options to components that emit events without declaring them, satisfying vue/require-explicit-emits: - TagChip (delete-tag, update-tag) - TagViewer (delete-tag, update-tag, new-tag) - HeaderMenu (submit) - TooltipButton (click) - PluginTasksTable / AgentTasksTable (refresh-tags) - AgentFileBrowser (openUploadDialog) - AgentExecuteModule (moduleChange, submitted) - AgentForm (refresh-agent) - ModulesTable (languages-changed) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Use stable v-for keys for mutable lists Replace array-index keys on lists that reorder with stable ids: - Chat.vue: assign an incrementing id to each pushed message and key on it (via a small addMessage helper) instead of the loop index. - Notifications: addNotification now stamps each notification with a unique id (timestamp + counter, stable across reloads since notifications are persisted). NotificationBell and the Notifications view key on item.id. Notifications are prepended, so index keys were genuinely unstable. afterRestore backfills ids on notifications that were persisted before ids existed, so they don't all key on undefined. Static / append-only lists (terminal output, author chips, info comments, common stagers, obfuscation configs, header menu) were left on index keys intentionally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add CHANGELOG entries for Vue quick-win modernization Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Migrate ESLint to v9 flat config on the official Vue stack Drop the legacy eslintrc + airbnb stack in favor of ESLint 9 flat config on the official Vue baseline: eslint-plugin-vue (flat preset) + @vue/eslint-config-prettier (skip-formatting) + Prettier as its own step. This is a tooling-only change — no source files were reformatted and no app behavior changed. Deps: - eslint ^8 -> ^9; add @eslint/js, globals, @eslint/compat. - Bump eslint-plugin-vue to ^9.33 (exposes flat configs). - Replace eslint-config-prettier with @vue/eslint-config-prettier ^10 (skip-formatting export: disables stylistic rules, keeps prettier/prettier OFF so Prettier still runs separately via `format` / `format:check`). - Remove @vue/eslint-config-airbnb and @rushstack/eslint-patch. Config (eslint.config.js): @eslint/js recommended, eslint-plugin-vue flat/recommended, @VUE skip-formatting, then a project block (applied last, so its overrides win over the presets/prettier disables — matching the precedence the old eslintrc `rules:` block had over `extends`) with browser globals and all preserved overrides ported verbatim. .gitignore is honored via includeIgnoreFile (replaces --ignore-path); dist/dist_electron ignored explicitly. Node globals applied to *.config.js + e2e/**; vue.config.js linted as CommonJS. The old mocha __tests__ override is dropped (no unit tests; tests are Playwright e2e). import/no-unresolved decision — DROPPED (not enforced). The Vite build already fails on unresolved imports, so enforcing it in ESLint would mean maintaining a separate resolver for the "@" -> ./src alias for no added safety. The whole eslint-plugin-import dependency is dropped (also drops import/no-cycle and the src/plugins/vuetify.js override, which are only relevant with an import plugin). Because the source carries pre-existing inline `// eslint-disable import/...` comments and editing source is out of scope here, a tiny no-op stub registers those rule names so the directives resolve (flat config errors on directives naming undefined rules). Using eslint-plugin-import-x instead would drag in @typescript-eslint/utils — unwanted in a no-TypeScript project. Rule relaxers added to preserve parity (not airbnb rules — ESLint 9 default changes / flat-config defaults): - no-unused-vars caughtErrors: "none" — ESLint 9 flipped the default to "all", newly flagging 3 pre-existing `catch (err)` bindings. Pinned back to the ESLint 8 behavior to avoid editing source. - linterOptions.reportUnusedDisableDirectives: "off" — flat config defaults this to "warn"; off matches the eslintrc-era default and prevents the vestigial inline disable comments (for now-unconfigured airbnb/core rules) from warning or being auto-stripped by `--fix`. Lint script -> "eslint --fix ." (drops invalid --ext / --ignore-path flags); format / format:check untouched. Verified: `yarn lint` clean (0 errors, 31 warnings — all pre-existing vue/no-v-html + vue/require-explicit-emits, same as before); `yarn build` succeeds; `yarn format:check` clean (no formatting diff); --print-config confirms vue rules + browser globals + overrides resolve. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add CHANGELOG entry for ESLint flat-config migration Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Extract ANSI helpers and auto-refresh polling into shared code
Pure refactor; behavior, polling cadence, and rendered output unchanged.
Part A — ANSI helpers → src/utils/ansi.js
- New util exports stripAnsi/isAnsi/ansiToHtml (moved verbatim, same regex
and per-call `new AnsiUp()` semantics).
- AgentTasksTable, PluginTasksTable, AgentTerminal, AgentShellSession now
import from the util; the two terminals keep their `ansiToHTML` template
name via an `ansiToHTML: ansiToHtml` method alias.
- AgentJobs intentionally reuses a single stateful AnsiUp instance across
calls, so the util re-exports the AnsiUp class and AgentJobs keeps its
module-level instance (only the import source changed) to preserve output.
- No `ansi_up` import remains outside src/utils/ansi.js.
Part B — auto-refresh polling → src/composables/useAutoRefresh.js
- New composable owns the interval lifecycle and clears it on
onBeforeUnmount, exposing { autoRefresh, start, stop }.
- AgentTasksTable, PluginTasksTable, and AgentsTable wire it via a minimal
setup() (getCurrentInstance().proxy bridges the Options API callbacks) and
drive start()/stop() synchronously from their existing prop watchers, so
each component keeps its exact interval (8000ms), fetch callback
(debouncedGetTasks vs getAgents), and immediate-fetch timing.
- Removed the per-component refreshInterval data field, inline setInterval,
and beforeUnmount clearInterval.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add CHANGELOG entry for shared ANSI/auto-refresh extraction
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add Vitest unit-test harness and first batch of data-layer tests Stand up a Vitest unit-testing setup alongside the existing Playwright e2e suite (which is untouched) and seed it with high-signal tests for pure data -layer logic. - deps: vitest@3.2.4, @vue/test-utils@2.4.10, @pinia/testing@0.1.7, happy-dom@20.9.0 (pinned for Vite 5 / Vue 3.5 / Pinia 2 compatibility) - vite.config.js: switch to defineConfig from vitest/config; add test block (globals, happy-dom env) scoped to src/ so Vitest stays out of e2e/ - package.json: add test:unit (watch) and test:unit:run (one-shot) scripts - .eslintrc.js: replace the stale mocha override with Vitest globals so test files lint cleanly - tests: listener-module (addListener dedup, removeListener, getListeners status flow, listenerNames/templateIds getters), bypass-module (mergedBypassNames merge/dedup), and utils/tags fetchDedupedTags 15 tests, all passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address first-pass review: cover error branches, tighten eslint glob Review feedback from the pr-review-toolkit pass: - listener-module.spec: add tests for killListener (api call + state removal), fetchAutorunTasks (success extraction + reject-returns-[] / state-intact), and saveAutorunTasks (records-wrapping payload shape + error swallowing) — these were the highest-signal untested branches. Silence console in beforeEach so the error-path tests don't pollute output. - bypass-module.spec: add vi.clearAllMocks() in beforeEach for consistency. - .eslintrc.js: scope the Vitest globals override to src/ so it no longer matches the e2e/ Playwright specs; refine the mock comment. 20 unit tests, all passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add CHANGELOG entry for Vitest unit-test harness Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Trim unused test deps; use node Vitest environment No unit test touches the DOM or mounts a component, so happy-dom and @vue/test-utils are unused. Drop them and run under the default node environment; re-add them with the first component test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix getListeners leaving status stuck on "loading" when fetch rejects A rejected listeners fetch previously left status at "loading" forever (hanging spinner, no error surfaced). Wrap in try/catch matching the agent-module convention (log + status="error"), and add a reject-path test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…4, Vitest 4 (#289) Moves the framework stack forward to the latest majors, removing the Vite 5 / Pinia 2 constraint that pinned the Vitest harness (PR #282) to vitest@3.2.4 / @pinia/testing@0.1.7. - Vite 5 -> 8 (Rolldown/Oxc) and @vitejs/plugin-vue 5 -> 6 - Pinia 2 -> 3 and pinia-plugin-persistedstate 3 -> 4 - Vitest 3 -> 4 and @pinia/testing 0.1 -> 1 - vite-plugin-vuetify, Vuetify, and vue-router left untouched (work as-is) persistedstate v4 changes handled: - Renamed the application-module persist hook afterRestore -> afterHydrate (v4 rename); it still re-applies the axios instance url/token after rehydrate. Wrapped the setInstance call in try/catch + an explicit log, since v4 swallows hook errors when debug is off and this hook is the only path that rebuilds the axios instance after a reload. - chatUnreadCount is now correctly omitted: v4 honors `omit`, which v3 silently ignored, so the field was previously being persisted. CI: bumped all workflows from Node 20 to Node 22 to meet Vite 8's Node baseline (^20.19.0 || >=22.12.0). Verified: yarn install (clean), test:unit:run (20/20), build (ok), e2e (40/40 serial, matching CI's workers=1), lint (31 warnings/0 errors, unchanged from base), and a direct persistedstate v4 localStorage round-trip check. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…able (#287) * Fix socket handler + timer leaks in Chat via useSocketHandlers composable Chat.vue is a v-if'd consumer of the app's single socket (owned by SocketNotifications). On each mount it registered chat/join, chat/leave, chat/message, and chat/participants listeners with anonymous callbacks but unmounted() only emitted chat/leave — it never removed them. Because the socket outlives Chat, toggling the chat widget setting accumulated duplicate handlers, causing duplicate message processing and inflated unread counts. mounted() also started a 1s setTimeout with no clearTimeout on unmount. - Add src/composables/useSocketHandlers.js: useSocketHandlers(socket) returns on(event, handler) (records each pair) and emit(...) passthrough, and via onUnmounted calls socket.off for every registered handler. It manages HANDLER lifecycle only — never opens or closes the socket. - Chat.vue: add a minimal setup(props) that bridges the composable's on/emit into the Options API instance; route the four chat/* registrations through on() so they auto-remove on unmount. Capture the history timer id and clearTimeout it in unmounted(). Handler logic (unread counting, historyLoaded gating, skipping own messages) is unchanged; template is unchanged. - SocketNotifications.vue: remove the "Opening Socket"/"Closing Socket" debug logs. Did NOT adopt the composable here — its setAgentHandlers does conditional socket.off and re-registers on subscription changes, which doesn't fit the record-and-cleanup model, and those handlers are torn down with the socket anyway (no leak). Connection creation/teardown untouched. Chose the setup() bridge over component-level bookkeeping because the shared cleanup logic must live in the composable, and onUnmounted only fires from setup; the socket prop is stable (Chat fully remounts when toggled) so the one root-scope prop read is intentional. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add CHANGELOG entry for chat socket handler leak fix Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Document useSocketHandlers @throws contract in JSDoc The composable fails fast when no socket is provided; note that in the JSDoc so callers know the contract is enforced at runtime. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add design spec: replace axios with native fetch
Records the decided HTTP-layer direction for Starkiller: remove axios
(^0.24.0) and qs in favor of native fetch behind a centralized
src/api/http.js wrapper, with a Vitest contract suite built first and a
phased migration across the 13 api modules + 4 bare login calls.
Investigation/design only — no implementation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revise HTTP fetch spec per adversarial review
Addresses 3 critical + 3 medium + 1 low finding:
- Normalize leading-slash-free paths in request() (18 call sites)
- Axios-compatible error shape (err.response.{status,statusText,data})
so handleError, extractErrorMessage, and direct readers (ListenerEdit)
keep working unchanged
- Forced exception #2: functional-form calls return parsed body; their
consumers (FileUploadDialog/Downloads/Settings) updated
- safeParse no longer trusts content-length; defensive try/catch parse
- Differential qs-parity test on the real getTasks param shape
- Spell out all 3 blob/text download paths + triggerDownload helper
- Step 0 blast-radius grep; step 1 rewires setInstance to http.js
- Note module-api consumer (AutoRunModules) tolerates new handleError
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revise HTTP fetch spec per adversarial re-review (round 2)
Addresses 1 critical + 2 medium + 1 low:
- toQuery normalizes "+" -> "%20" so query bytes stay identical to qs
(URLSearchParams encodes spaces as "+"); keeps the differential test valid
- §7 test must include a free-text query value with a space
- Login/refreshMe stay on raw fetch in the store (NOT request()), preserving
interceptor-free behavior; drop the auth flag from the wrapper entirely
- Correct forced-exception-#2 consumer list: only FileUploadDialog reads .data;
Downloads.vue/Settings.vue verified no-ops
- Document accepted divergence: malformed 2xx body -> undefined (vs axios string)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fold in round-3 advisory findings (gate passed)
Adversarial review round 3 verdict: APPROVE (0 critical). Advisory:
- Raw-fetch login must check !res.ok before reading the body (it bypasses
request()'s centralized throw); spec now mandates a rawFetchJson() helper
so a wrong-password 401 can't slip a bad token through
- Restore Pragma/Expires cache headers in the getDownload example
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add implementation plan: replace axios with native fetch
9-task TDD plan: Vitest infra, the http.js wrapper + contract suite,
dual-init wiring, raw-fetch login, staged migration of all 13 api modules,
and cleanup removing axios + qs. Each commit keeps the app working.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: add Vitest unit-test infrastructure
* feat: add native-fetch http wrapper with contract tests
* refactor: initialize http wrapper from setInstance during migration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: migrate login and refreshMe to raw fetch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: migrate tag-api to fetch wrapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: migrate credential/plugin/agent-task apis to fetch wrapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: migrate download-api to fetch wrapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: migrate remaining api modules to fetch wrapper
* refactor: remove axios and qs dependencies
Completes the axios→native-fetch migration. Deletes src/api/axios-instance.js,
switches the store's setInstance import to @/api/http, removes the temporary
qs differential test from http.spec.js, and runs `yarn remove axios qs`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address review findings (download error handling, error-body guard, tests, stale comments)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: simplify fetch migration code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Remove superpowers planning docs and add CHANGELOG entry
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Apply Prettier formatting to the new fetch wrapper + migrated modules
CI's lint job runs yarn format:check (Prettier --check) in addition to
ESLint; that step was failing on the new files because I only ran eslint
locally. Pure whitespace — Prettier wrapping long lines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address PR feedback: shorthand methods, harden safeParse, route refreshMe through wrapper
- Add request.get/post/put/delete shorthand to http.js and migrate the 13
api/*.js modules to use it (~75 call sites). Restores the call-site
ergonomics axios had before this migration.
- Make safeParse distinguish empty body (return undefined for 204/DELETE)
from a non-empty body that fails to parse — throw an axios-shaped error
so a reverse-proxy 200-HTML page or truncated response surfaces via the
existing .catch(handleError) chain instead of crashing a list consumer
with TypeError: undefined.records.
- Route refreshMe through request.get() so a 401 triggers logout-on-auth
like every other authenticated endpoint, and await it at Settings.vue so
failures surface in the surrounding try/catch instead of dropping as an
unhandled rejection. Only login retains the raw-fetch bypass — that's
the case where logout-on-401 is actively wrong.
- Add a setInstance preflight check so calling request() before the API
client is initialized throws a clear error instead of looking like a
network outage via the catch-all connectionError++ path.
Tests:
- New application-module.spec.js pins the login bypass invariant: 401 and
network-failure on login must NOT trigger logout() or bump
connectionError. The whole point of the bypass; previously asserted by
nothing.
- Refactor the blob test to assert the .headers.get()/.blob() contract
that download-api actually consumes, not identity with the mock object.
- Add tests for skipNulls falsy-value semantics (0/false/"" survive),
caller-provided headers merging with auth, and Content-Type override
behavior.
- Add shorthand-method contract tests (delegates to GET/POST/PUT/DELETE,
no-body POST, FormData passthrough, params forwarding).
- Update the "non-JSON 200 body" test to reflect the safeParse fix.
CHANGELOG: Fixed entry for FileInput auto-select side-effect (createDownload
previously returned the response wrapper, so .id was undefined).
51/51 unit tests pass; lint clean; prettier clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Drop semver dep in favor of inline version comparator
Only two call sites in App.vue, both using semver.satisfies() with
trivial single-comparator ranges ("<5.2", ">=4.0"). The full semver
package is ~260KB and is one of the surfaces flagged in our dependency
audit; this commit replaces it with a ~30-line satisfies() that handles
the operators we actually use (>=, <=, >, <, =) and matches semver's
behavior of returning false for non-numeric/invalid versions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Drop table dep in favor of inline ASCII table renderer
Two call sites in AgentTerminal.vue, both rendering simple bordered
ASCII tables for the terminal view. The `table` package (3.1 MB +
transitive ajv/lodash.truncate/slice-ansi/string-width/strip-ansi) is
flagged in our dependency audit. This commit replaces it with an
~80-line renderer matching the package's output byte-for-byte for the
two configs we use (vetted via Vitest goldens captured against the
upstream package before removal).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address PR-review feedback: harden inline helpers, add CHANGELOG entry
CHANGELOG
- Added Removed entries for `semver` and `table` under [Unreleased]
alongside the existing axios/qs removal entry.
version.js
- Strip a leading "v" in parseVersion so "v5.2.0" parses the same as
"5.2.0", matching semver.satisfies's tolerance. Without this, a stray
"v" in Empire's version response would silently kill the >=4.0
socket-panel gate and the <5.2 compatibility warning at App.vue. The
endpoint is contractually plain dotted, but defensive parity is cheap.
- Updated spec: dedicated v-prefix block asserting the new behavior.
ascii-table.js
- Honor embedded "\n" in cells as forced line breaks (matches the
upstream `table` package). Without this, a literal newline in a
module description visually shattered the table.
- Auto-size columns to the widest sub-line (not the raw string length)
so a "one\ntwo" cell sizes as 3, not 7.
- Guard `wrapToWidth(_, 0)` — without it, `i += 0` spins forever and
hangs the browser tab. Reachable for any future caller that sets
`columns: { N: { width: 0 } }`.
- Throw on row-length mismatch instead of silently truncating extras /
padding missing cells. Restores the loud-failure contract `table`
provided.
- Warn on non-primitive cells instead of silently rendering
"[object Object]" — Empire's contract is strings/booleans, so a
regression now surfaces in the console.
- Extracted BOX_BORDER constant exported from ascii-table.js and
imported by both AgentTerminal.vue and the spec, so the byte-for-byte
parity test can't pass against a stale local copy while production
renders differently.
- Spec: pinned the upstream-capture version (`table@^6.8.1`) in the
goldens comment so future recaptures are deterministic. New tests for
each safeguard above.
* Second-pass review fixes: case-insensitive v-strip + freeze BOX_BORDER
version.js
- parseVersion now strips a leading "v" OR "V" (was lowercase-only).
node-semver itself uses /^[vV]/, so the comment claiming "matches
semver tolerance" was aspirational. Closes the same silent-gate
failure mode the original v-strip addressed, but for capital V.
- Spec: added "V5.2.0" to the v-prefix block.
ascii-table.js
- Object.freeze(BOX_BORDER). Vue puts the imported singleton inside a
reactive proxy in AgentTerminal.vue's data(); a stray mutation via
`this.tableConfig.border.X = ...` would proxy-trap through and
poison every subsequent render across all consumers. With freeze,
that mutation now throws loudly under strict mode (which Vue
components run under) instead of silently corrupting state.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add the server-side validation feedback / GeneralForm refactor from #290, which had no changelog entry, and compress the Unreleased section to the terse one-line-per-change style used in 3.2 and earlier releases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v3.6.0 into private-main
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was automatically generated by the
release-public-startworkflow.This PR should be merged with a merge commit, not a squash commit.
Merging this PR will trigger a tag and release automatically.