mirror of
https://github.com/saltbo/zpan.git
synced 2026-09-19 01:51:11 +08:00
b7f7fa7ecd1283d27d5cbca9cd3b8974dc6ea127
14
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
979afd20e7 | style(downloader): format client tests | ||
|
|
3fdc7b4ae0 | refactor(downloader): reorganize cmd downloader runtime | ||
|
|
26b7a1d8a4 | fix(downloader): preserve failed upload results for retry | ||
|
|
cf7b1de112 | fix(downloader): consolidate polling into heartbeat | ||
|
|
c7f3d11793 |
[codex] Add admin storage connection testing (#475)
* feat(storage): add admin connection testing Agent-Profile: https://agent-kanban.dev/agents/2673e70e0085f4e0 * fix: correct storage CORS guidance Agent-Profile: https://agent-kanban.dev/agents/2673e70e0085f4e0 --------- Co-authored-by: Jordan Park <jordan-park@mails.agent-kanban.dev> |
||
|
|
399609b121 |
fix(downloader): drive stop-control from the task poll, make reporting pure (#464)
The progress-report path was making control decisions by string-matching error bodies — and missed the suspended case entirely, so a billing-suspended task kept downloading while the UI showed Suspended. Reporting is now pure telemetry: updateTask just syncs progress/status and a failed report is logged, never acted on. All stop-control (pause/cancel/suspend) flows through the existing task poll: - AssignedControlTasks now also queries 'suspended'; cancelRunning cancels the running download with errTaskSuspended and leaves the server-owned suspended status untouched. - Removed errBillingPaused, the insufficient_credits string-match, and the isControlledTaskUpdateError/resolveControlledTaskUpdate error-string matching. pause/cancel already flowed through the poll; the report-path handling was a redundant, brittle second mechanism. Worker-only; no server change (status=suspended is already pollable and keeps assignedDownloaderId). Tests cover the suspended poll cause and the control status query. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5b8495bc3d |
fix(downloader): report seeding stopped so the dashboard clears it (#463)
When a retained seed was cleaned up (expiry/ratio/cache/missing), the worker removed it from aria2, the ledger, and local disk — but never told the server seeding had ended. The task's last runtime report (phase=seeding, Seeding.Active=true) stuck, so the dashboard showed completed tasks as seeding forever. reportRetainedSeedsStopped only ran on worker shutdown, not on per-seed expiry. - cleanupRetainedSeed now reports the task runtime as completed/not-seeding after a seed is cleaned, so the UI clears it. Task status stays completed. - clearStaleSeedingReports runs once at startup: lists this downloader's completed tasks the server still marks seeding (new client SeedingTasks), and reports stopped for any the worker is no longer actually seeding (after reconciliation), clearing entries left stale by older builds. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b959e5e6bf |
fix(downloader): supervise managed engines and make the CLI go-installable (#461)
Three related changes hardening the remote downloader, bundled because the module-path rename touches every file — splitting would leave a mid-history commit that doesn't build. Engine supervision: a managed engine subprocess (aria2c / qbittorrent-nox) that exits unexpectedly now crashes the worker. watchEngineProcess waits on the child, logs the exit at error level, and cancels the run context so `downloader up` returns non-zero; the container (restart: unless-stopped) then restarts the whole stack. Previously the exit error was discarded and the worker kept heartbeating as healthy while every task failed against the dead RPC. A deliberate shutdown kill is told apart from a crash via a stopping flag. aria2 GID recovery: waitAria2 re-discovers the live GID via findTask when aria2 reports 'GID ... is not found' mid-download (e.g. after an aria2 restart) instead of failing the task. Adds isAria2GIDNotFound, which matches tellStatus's message format that isAria2DownloadNotFound missed. CLI install: move the entrypoint to the module root (cmd/main.go) and rename the module github.com/saltbo/zpan/cmd -> github.com/saltbo/zpan so `go install .` from cmd/ yields a zpan binary. Internal imports shorten to github.com/saltbo/zpan/internal/...; the Dockerfile builds the module root. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7b8c8c915e |
refactor(api)!: unify object upload + rework delete/trash lifecycle (#448) (#454)
Resolve #448 — one upload entry point and an AIP-164 soft delete. Upload: POST /objects now returns size-decided upload instructions { sessionId, partSize, urls }; the server picks single PutObject (<=5 GiB) vs 5 GiB-part multipart (>5 GiB) and rejects >5 TiB. The client PUTs each slice, reads its ETag, then POSTs them to POST /objects/{id}/uploads/{sid}/completions (returns the live object). DELETE /objects/{id}/uploads/{sid} aborts and discards the draft. Trash: matters.status drops 'trashed' (enum is {draft,active}); trash is tracked by the existing trashedAt timestamp. DELETE /objects/{id} now soft-deletes; the recycle bin lives under /trash/objects (list roots, get, restorations, purge). Empty-trash is a frontend loop over roots. BREAKING CHANGE: - removes PUT /objects/{id}/status and POST /objects/{id}/uploads - PUT .../uploads/{sid}/status -> POST .../uploads/{sid}/completions {parts} - DELETE /objects/{id} flips hard-purge -> soft-delete; permanent purge moves to DELETE /trash/objects/{id} - DELETE /trash removed; restore is POST /trash/objects/{id}/restorations - matters.status enum loses 'trashed' (migration backfills to trashedAt) The migration swaps the matters_active_name_uniq partial index to exclude trashed rows (WHERE status='active' AND trashed_at IS NULL). The single-PUT presign is header-free so the uniform slice uploader's raw PUT matches the S3 signature. Go downloader client + agent reworked to the unified flow. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e132cb9e41 |
feat(openapi): complete API coverage with truthful schemas + unified error handling (#442)
* feat(openapi): complete API coverage with truthful schemas + unified error handling
Migrate every resource router to `@hono/zod-openapi` so the global OpenAPI
document (and the SDKs generated from it) covers the whole product API, not just
~15% of it. The document now describes 25 resources with named component schemas,
operationIds, and accurate response shapes.
What changed:
- Unified error handling: a single `mapDomainError` (DownloadError, ObjectUpload-
SessionError, NameConflictError, StorageQuotaExceededError, BackgroundJobError,
WebDavPathError) wired into a global `app.onError`; handlers throw domain errors
instead of hand-rolling per-route try/catch. One shared `ErrorResponse` envelope.
- Shared http helpers (`server/http/openapi.ts`): generic `jsonContent`/`jsonBody`/
`errorResponse` so the precise schema type reaches `createRoute` — typing
`c.req.valid()` and strictly checking `c.json()` returns (no widened `z.ZodType`).
- Schemas are the truth: response schemas are named (`.openapi('X')`), wire-shaped
(ISO-string timestamps via per-resource `toXDTO` mappers where the domain type
uses `Date`), and strictly enforced against handler returns. The strict pass
surfaced and fixed several latent schema lies (e.g. transfer result shape,
download-task delete tombstone, object `purged`).
- operationId + summary on every route → clean SDK method names.
- Curated out of the public SDK (kept as plain routes): the `/r` redirect resolver,
store webhook receiver, internal telemetry endpoint, the PicGo/ShareX image
upload tool endpoint, the share download redirect, and cron-secret licensing
sync endpoints.
- Disambiguated user operationIds that collided with better-auth's admin API;
dropped `additionalProperties` schemas that oapi-codegen mis-generates.
- Regenerated the Go downloader client and realigned its hand-written wrapper to
the operationId-derived names.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(cmd): gofmt the realigned downloader client
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
91d10f9b97 |
feat(openapi): global OpenAPI document + Scalar UI, drop hand-written stubs (#440)
* feat(openapi): global OpenAPI document + Scalar UI, drop hand-written stubs
Replace the curated, partly hand-written "downloader" OpenAPI doc with a
single global document generated from the real routes.
- main app → OpenAPIHono; serve the aggregated spec at /api/openapi.json and
the Scalar reference UI at /api/docs. A resource appears in the doc as soon
as it is converted to `.openapi()` — no curation, no drift.
- enable better-auth's openAPI plugin; the auth/device flow now documents
itself at /api/auth/reference instead of hand-written route stubs.
- convert objects.ts and events.ts to self-documenting OpenAPIHono routes;
RPC types preserved (responses go through unwrap<T>, {id}→:id accessors hold).
- tag operations (Objects/Events/Download Tasks/Downloaders) + top-level tags
so Scalar groups them.
- delete server/openapi/downloader.ts and its device/object/events stubs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(openapi): merge better-auth schema into one doc; regen Go client from it
Make /api/openapi.json a single fully-generated document and drive the Go
downloader client from it — no hand-written/maintained spec.
- merge better-auth's auto-generated schema (auth.api.generateOpenAPISchema)
into /api/openapi.json, prefixed under /api/auth. The device-authorization
flow and the rest of the auth API now appear in one doc + Scalar.
- correct one upstream bug in the merge: better-auth advertises
POST /device/token as { session, user } but its handler returns the OAuth
token { access_token, token_type, expires_in } — override that one response
so the doc and the generated client match reality.
- rewire the Go-client codegen to generate from the merged document: a new
build-client-spec.ts boots the in-memory app, reads the real merged
/api/openapi.json, scopes it to the downloader's paths (device + downloads +
objects), prunes unreferenced components, strips security metadata, and
downconverts 3.1 nullable unions to 3.0 for oapi-codegen.
- regenerate docs/openapi/downloader.json + cmd/internal/openapi/client.gen.go
and adapt cmd/internal/client to the regenerated device types (inline request
bodies, optional pointer/number fields) and the 201-only object create.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
3402a1e099 |
refactor(api): RESTful resource-oriented API — drop /admin, status sub-resources, merge audience-split routers (#437)
* refactor(api): RESTful resource-oriented API — drop /admin, status sub-resources, merge audience-split routers Reorganize the entire HTTP surface around resource abstraction instead of business/audience abstraction. - Auth: authMiddleware is now soft + global for /api/*; gating is per-route (requireAuth/requireAdmin/requireTeamRole), so one resource path serves public, user, and admin callers (no security change — guards moved, not dropped). - Drop /admin from URLs; merge audience-split routers into one resource each (announcements, auth-providers, users, teams, quotas, invite-codes, site-invitations, downloaders, branding, audit). - State transitions -> PUT /:id/status: objects (confirm/trash/restore), download-tasks (pause/resume/cancel), background-jobs, image-hosting confirm. - Verbs -> noun sub-resources: objects/:id/copies, download-tasks/:id/attempts, background-jobs/:id/retries, site-invitations/:id/deliveries, licensing/pairings + /pairings/:code + refresh-runs, teams/:id/invite-links. - Config -> /api/site/* (branding, email, options, instance, changelog); ihost -> image-hosting; me + profiles + admin/users -> one /api/users (the :username slot also resolves the internal id, so the admin UI is unchanged). - External downloader OpenAPI contract cut over in lockstep. Frontend (rpc.ts + api.ts) and all integration/CF/unit tests updated to match. Typecheck (server + src), lint:http, biome, and all 4394 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(downloader): regenerate Go client + sync spec for the new RESTful contract The Go downloader agent (cmd/) and the BDD spec live in this repo, so they must move with the API: - Regenerate docs/openapi/downloader.json and cmd/internal/openapi/client.gen.go from the updated server OpenAPI. - Update the hand-written Go client: heartbeat -> /downloaders/me/heartbeats, register -> /downloaders, object confirm -> PUT /objects/:id/status, upload complete -> PUT .../status, abort -> DELETE .../uploads/:sid. Drop the now-dead union helpers (jsonBody/decodeJSON) and the bytes import. - spec: drop the obsolete teams invite-token-missing scenario (the route is now a path param) and add the auth-providers anon-public-list scenario (the merged GET serves the public list to anonymous callers). gofmt clean, go test (121) pass, lint:spec passes (418 scenarios covered). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(api): cover users admin detail/entitlements + getUser wrapper Close the patch-coverage gaps from the users-resource merge: add integration tests for GET /api/users/:id (admin detail, success + 404) and GET /api/users/:id/entitlements (success + 404), and a unit test for the getUser() api.ts wrapper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(e2e): update Playwright specs + global setup to the new RESTful paths The e2e specs make direct API calls / response matchers that bypass the SPA, so they need the new paths too: global-setup storage+options seeding (/api/storages, /api/site/options), image-host (/api/image-hosting, confirm via PUT /images/:id/status), object confirm in archive (PUT /objects/:id/status), announcements and site-invitations (/api/announcements, /api/site-invitations, /api/site/email). The cloud pairing action:'approve' is the external cloud API, left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(e2e): fix cloud-store instance pairing path to /api/licensing/pairings The cloud-store spec calls the INSTANCE pairing endpoint directly: POST /api/licensing/pair -> /api/licensing/pairings and the poll GET /api/licensing/pair/:code/poll -> GET /api/licensing/pairings/:code. /api/licensing/status and /binding are unchanged; /api/pairings is the external cloud API, left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(api): rename /api/site-invitations to /api/invitations Avoids visual proximity with the /api/site/* config namespace. Top-level /api/invitations is unambiguous — team invitations are nested under /api/teams/:id/invitations and invite codes under /api/invite-codes. URL-only change; the internal site-invitations naming stays (still the accurate concept). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(api): group resources by functional domain (URLs) Move non-core resources under functional-domain prefixes (not permission): - /api/site/* absorbs storages, auth-providers, audit-events, licensing, invitations, invite-codes (joining branding, email, options, instance, changelog) - /api/downloads/* = tasks + downloaders (regenerated OpenAPI + Go client) Core resources stay top-level. Updates app.ts, rpc.ts, OpenAPI doc + Go agent client, and all integration/CF/e2e tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(server): mirror functional-domain grouping in http/ and usecases/ dirs Reorganize source files to match the functional URL domains established for the routes, so the directory tree reflects the same grouping as the API: - http/{site,downloads,image-hosting}/ and usecases/{site,downloads,image-hosting}/ - dissolve the permission-based console/ dir — admin resources are grouped by domain (site), not by audience - console/user -> top-level (users is a core resource, not an admin-only one) Co-located tests move with their sources; relative imports and vi.mock paths updated for the new depths. Pure file/directory restructure, no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(server): finish structural cleanup — merge split admin routers, drop rename leftovers Three follow-ups from the directory-structure review, completing the one-file-per-resource and domain-named-file conventions: - Merge the last two audience-split router files into their resource file as a second export (matching branding/quotas/invite-codes/site-invitations): teams-admin.ts -> teams.ts (adminTeams), licensing-admin.ts -> licensing.ts (licensing + licensingAdmin). - Drop pre-rename filename leftovers now that the dirs carry the domain: http/image-hosting/{ihost,ihost-config} -> {images,config}; http/site/site-invitations -> invitations; usecases/site/{site-invitation,site-public-origin} -> {invitation,public-origin}; usecases/image-hosting/{image-hosting,image-hosting-config} -> {images,config}. - Group the loose store helpers under the store domain: http/{cloud-store-helpers,traffic-metering-utils} -> http/cloud-store/{helpers,traffic-metering}. Routes and exports unchanged; pure file/structure move. tests + co-located specs move with their sources. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(api): move announcements under /api/site, co-locate stray tests Announcements is instance-level, admin-authored content (like branding) — a site resource, not a top-level one. Move it under the site domain: - /api/announcements -> /api/site/announcements (mount, RPC base path, api.test, e2e spec) - http/announcements -> http/site/announcements; usecases/announcement -> usecases/site/announcement Co-locate the tests that drifted from their sources during the dir reorg (the 1:1-paired cf-test/unit tests belong next to what they exercise): - http/storages.cf-test.ts -> http/site/ (next to storages.ts) - usecases/{license-certificate,license-policy,license-refresh,licensing-admin}.test -> usecases/site/ (next to the licensing usecase; imports simplified to ./licensing) No behavior change beyond the announcements path. Routes/exports otherwise stable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(usecases): de-fragment the users and objects domains at the usecase layer The HTTP layer already serves these as single resources; consolidate their usecases to match, removing leftover files that mirrored the old split: - Fold me.ts (avatar) + profile.ts (public lookup) into user.ts — one user usecase with self/public/admin sections; drop the stale /api/me/avatar and /api/profiles/:username doc comments. Their unit tests move into user.test.ts. - Fold matter.ts (confirmUpload, draft→active) into object.ts — the objects domain is now under one "object" name (the Matter *type* stays in ports/). Importers updated; no behavior change. server tsc + lint:http + lint:spec clean; Node 4337 / CF 57 / libsql 6 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(usecases): fold sub-concern usecases into their resource (one file per resource) Consolidate the usecase layer so each resource is a single source file: - object.ts absorbs object-upload-session, purge, and save-to-drive (its upload-session / recursive-purge / save-to-drive sub-concerns) - share.ts absorbs share-notification and share-ref External importers re-pointed (trash, redirect, entry-node, workers/scheduled, http/share-utils, and the surviving integration/cf tests). share.ts now pulls copyMatterToOrg/saveShareToDrive from object. share.test.ts asserts the real notification+email fan-out now that dispatchShareCreated is intra-module. Shared domain services (storage-usage, cloud-traffic-metering, captcha) stay separate — they're used by many resources. 5 files removed; no behavior change. Node 4337 / CF 57 / libsql 6 green; tsc + lint:http + lint:spec clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(http): collapse concern-split integration tests into one per resource Each resource now has a single Node integration test file; the scenario-split files fold into their resource's main: - objects-quota + object-multipart-live -> objects.integration.test.ts - me + profile -> users.integration.test.ts - quotas-listing -> quotas.integration.test.ts - teams-admin -> teams.integration.test.ts - share-public -> shares.integration.test.ts (share-public.cf-test stays — CF runtime) Helpers de-duplicated or scoped per describe; all [spec:] breadcrumbs preserved (lint:spec still 418). 7 files removed, all 4337 tests retained. The multipart-live block now restoreAllMocks so it exercises the real S3 gateway (latent bug fixed). Node 4337 / CF 57 / libsql 6 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: finish test-file reorg + convert cloud licensing to a real Playwright e2e Directory grouping (finishing the reorg): auth tests -> http/auth/, cloud-store test -> cloud-store/, captcha + signup-mode -> usecases/site/ (with import-depth fixes the moves needed). One file per resource at the test layer: - save-to-drive.integration + purge.integration -> object.integration.test.ts - save-to-drive.cf-test -> object.cf-test.ts - share-notification.integration -> share.integration.test.ts - webdav.e2e (a vitest integration test, not Playwright) -> merged into webdav.integration.test.ts Cloud licensing e2e: e2e-cloud-integration.test.ts was a vitest file mostly duplicating existing integration coverage (licensing-admin.integration + licensing-cloud.test) and the pairing e2e already in cloud-store.spec.ts. Replaced with a real Playwright e2e (e2e/licensing.spec.ts): pair+approve -> assert a Pro gate opens -> unbind -> assert it closes. Shared pairing helpers extracted to e2e/helpers.ts (cloud-store.spec now imports them). run-cloud-e2e runs both cloud specs in one tunnel; CI grep-invert excludes the new title from the no-cloud run. tsc + lint:http + lint:spec clean; Node 4337 / CF 57 / libsql 6 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(server): move the cloud-store domain under store/ (matches /api/store) Following the dir move: http/cloud-store/* -> http/store/*, the cloud-store + cloud-traffic-metering usecases -> usecases/store/, and the top-level cloud-traffic-metering http integration test -> http/store/. The http/cloud-store.ts barrel now re-exports from ./store/*. All importers + moved-file imports rewired. tsc + lint:http + lint:spec clean; Node 4337 / CF 57 / libsql 6 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(server): drop the cloud- prefix under store/ now that the dir carries it - usecases/store/cloud-store -> store.ts; cloud-traffic-metering -> traffic-metering.ts - http/store/cloud-store.integration -> store.integration; cloud-traffic-metering .integration -> traffic-metering.integration - the http barrel http/cloud-store.ts -> http/store/index.ts (re-exports from ./storefront + ./webhooks); app.ts imports './http/store' store/ is now uniformly named (storefront/webhooks/helpers/shared/traffic-metering + store + index). tsc + lint:http + lint:spec clean; Node 4337 / CF 57 / libsql 6. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(e2e): licensing spec asserts the bind/unbind lifecycle, not a pro-only gate The cloud E2E account is business-tier; its pairing certificate does not grant open_registration (that's why the old vitest test seeded a local pro cert for that assertion). Assert the edition-agnostic licensing lifecycle instead: pairAndApprove (binds + waits active) -> unbind -> /status reports bound:false. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6521d1b722 |
feat(events): unify SSE stream, replace frontend polling (#432)
* feat(events): unify SSE stream, replace frontend polling Add a single /api/events SSE endpoint that multiplexes domains via named events. Jobs and notifications are always-on; download tasks are an opt-in per-connection subscription carried in the EventSource URL, so the server only polls what an open page needs and a browser tab holds one connection. - Replace refetchInterval polling: sidebar active-job badge, tasks list, notification unread count - Fold the download-tasks SSE into the unified endpoint; remove the now-dead /api/download-tasks/events route, downloadTaskEventsUrl, downloadTasksUrlApi - Client subscription registry: useServerEvents (single connection, reconnects when the merged subscription query changes) + useServerEventSubscription - Decouple the keep-alive heartbeat (25s idle) from the 2s data poll Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(downloader): regenerate OpenAPI spec/client after dropping /events Removing /api/download-tasks/events from the unified-SSE refactor drifted the generated downloader OpenAPI doc and Go client (caught by openapi:downloader:check in CI). Regenerate both. With the events operation gone, the single-value assignedTo enum constant collapses from GetApiDownloadTasksParamsAssignedToMe to Me, so update the hand-written client reference to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(events): cover unified SSE store and endpoint Codecov flagged the new SSE code as uncovered. - Unit-test the pure server-events-store (merge/sort query key, listener notifications, subscription lifecycle) - Integration-test GET /api/events: 401 unauthenticated, and that an authed user with a queued job receives jobs + notifications events - Exclude useServerEvents.ts (EventSource/React-effect glue, not unit-testable without a DOM) from coverage, matching the existing src/routes & src/components exclusions; its logic lives in the now-covered store Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(e2e): stop waiting on networkidle now that SSE is always connected The unified /api/events stream is mounted on every authenticated page, so the network never goes idle and waitForLoadState('networkidle') hangs until the job times out. Drop the four networkidle waits (sign-in helper + image-host reloads); subsequent navigations and element auto-waits already gate readiness. Also broaden the /api/events test to cover the abort and error branches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f31278b434 | refactor(cli): move zpan command module to cmd |