Tags: anobaka/Bakabase
Tags
fix: refresh resource list cards after cache rebuild
Refreshing a resource's cache (single or batch) rebuilt the cover/playable
cache on the backend but never told the frontend, so the resource list kept
showing stale cover and play-control state until a manual reload. Single
refresh relied on a frontend-only reload that the batch path bypassed, the
cover thumbnail URL had no cache-busting token, and play-control resolution
cached stale discovery results.
Backend:
- ResourceService.RefreshResourceCache now publishes PublishResourcesChanged
after each rebuild, so both single and batch (which loops this method) emit
a per-resource change. Deliberately not PublishResourceCoverChanged, which
would trigger cover-cache invalidation and delete the cache just rebuilt.
- New ResourceChangePushService bridges OnResourceDataChanged to the existing
UI SignalR hub (GetIncrementalData key "Resource"), aggregating bursts over
a short window and pushing only the changed resource ids.
Frontend:
- New transient ResourceChangedChannel (no retained store) fanned out from the
UIHub "Resource" push; the active resource tab reloads just the ids it shows
via reloadResources(..., { forceRefresh: true }).
- reloadResources stamps a client-only reloadToken on force-refreshed
resources. ResourceCover appends it (with the existing reloadKey) to the
thumbnail URL so a regenerated image at an unchanged path is refetched.
- usePlayableItemResolution treats a reloadToken bump like a resource change,
resetting its SSE discovery cache so a stale (often empty) result no longer
overrides the freshly returned backend playable items.
Fixes #1212
feat(cache): batch "refresh cache" for multi-selected resources Right-clicking more than one selected resource now offers a "Refresh cache for N resources" action. Refreshing a large selection rescans the filesystem and regenerates thumbnails per resource, so it runs server-side as a cancellable BTask (progress-reported) instead of blocking the request or firing N individual calls from the client. - IResourceService.RefreshResourcesCache: sequential batch over the existing per-resource refresh, with progress and per-resource error isolation. - POST /cache/resources/refresh enqueues the batch as a BTask (ReplaceIfExists so a new request supersedes a running one). - Context menu item shown only for multi-selection; single-selection keeps its existing synchronous refresh. Regenerated SDK and added en/zh localization for the new task + menu item. Closes #1210 https://claude.ai/code/session_01L3vo9qnAZSRPjUkG8tZvWU
feat(workflow): typed-flow workflow engine + drag-drop editor
Introduce the `Bakabase.Modules.Workflow` module: a trigger → activity
chain executor where items flow between nodes carrying a semantic
item-type tag (e.g. `item.pixiv.illust`, `item.searchQuery`). The
editor walks the chain client-side to gate which activities are
addable at each position and automatically inserts an AI transform
node to bridge incompatible types.
- Backend: Trigger / Activity / ItemTypeDescriptor abstractions +
registries + `IWorkflowEventBus`. The runner records per-step
in/out/failed counts on a `WorkflowRun` row for the run-history
drawer. Built-in triggers: `subscription.updated` (fired by the
subscription module) and `downloader.completed` (fired by the
downloader on terminal status). Built-in activities: title filter,
generic AI transform, ExHentai search → first gallery, ExHentai
enqueue download, generic send-notification (interpolates
`{{field}}` against the item's JSON shape).
- Frontend: dnd-kit drag-drop editor, grouped activity picker with
third-party icons via `GroupLabel`, ItemTypePill that shows the
current item shape + reflected fields, run-history drawer with
per-step funnel. Trigger / activity / item-type names are localized
via per-kind i18n keys with the server's English display name as
fallback.
- Integration: `DownloadTaskService` publishes `downloader.completed`
events; menu order under "Tools" promotes Subscription + Workflow
immediately after the File-name modifier so the cross-cutting
surfaces sit with the rest of the file pipeline.
- Wire-up: solution / DI / DbContext / EF migrations / SDK regen / i18n
registration land here so the previous two commits' modules become
reachable end-to-end.
Closes #1191
fix(resource): scroll page indicator + auto-named tabs from filter va…
…lues
Two resource-page fixes shipped together because both edit the same
tab/grid stack.
1) Scroll-driven page number stopped updating.
ResourceTabContent.onScroll mapped the viewport-center resource to a
page via `pageContainerRef.current?.querySelectorAll("div[role='resource']")`,
but ResourceCard's `role="resource"` was removed by eslint a11y
(it isn't a valid ARIA value). The selector returned zero matches
and the page chip went silent. Switch to `div[data-id]` — already
unique to ResourceCard root divs.
2) Auto-generated tab names from selected filters.
New `buildAutoTabName(form)` walks `form.group.filters` recursively
and renders each enabled filter's bizValue as comma-separated text
matching the property type's light variant. ResourceTabContent
bubbles its live searchForm up; FilterPanel reports pre-debounce
edits via `onSearchFormLiveChange` so the chip tracks filter edits
in real time rather than waiting for the 1s auto-search. The page
keeps a per-tab `liveSearchForms` map and derives display name as
`s.name || buildAutoTabName(liveSearchForms[s.id]) || "Search N"`
(localized) — so un-mounted tabs beyond the LRU cap and just-created
tabs with no filters still read as "Search 1" rather than empty.
Double-click pre-fills the input with the current display name;
Enter / Blur / Escape all commit (per spec — entering edit mode
locks the name). Clearing the input and committing is the way back
to auto-mode.
Backend: drop the "{Search} {N}" auto-naming in BuildNewSavedSearch
(and the now-unused translationOfSearch param + IBakabaseLocalizer
dependency in ResourceController) so new SavedSearches ship with
Name = "" and the frontend's auto-name actually wins. Existing tabs
with auto-numbered names keep them.
Fixes #1187
Closes #1188
docs(claude): warn about literal skip-ci substrings in commit bodies GitHub matches the directive anywhere in the message, so prose that mentions it (e.g. explaining a manual-override path) will silently skip the pipeline. Just bit us on 0a428be. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(btask): fluent builder, transitional statuses, heartbeat and… … UI cleanup Six related improvements landed together to address the points raised in #1183 (slow-feeling pause/stop, opaque builder API, drifting remaining time, simplistic background page, scattered status checks, no easy way to manually exercise the lifecycle). 1. Pausing / Resuming transitional statuses Pause()/Resume() now flip status to a transitional state synchronously so the chip stops lying about "Paused" while the task is still doing CPU/IO work between yield points. OnPause/OnResume from PauseTokenSource then promote to the final Paused / Running state when the body actually cooperates — matching the existing Cancelling behavior. 2. Heartbeat in BTaskHandler While the task body is in any active state (Running / Pausing / Resuming / Cancelling), a 1s heartbeat triggers _onChange so the SignalR push refreshes elapsed / remaining derived from handler.Sw.Elapsed. Without it the frontend timing simulator drifted when percentage plateaued. EstimateRemainingTime now extrapolates from live Sw.Elapsed instead of the last percentage-change snapshot. 3. Fluent BTaskBuilder API BTaskHandlerBuilder kept 16 fields with no signal as to which were required. BTaskBuilder.Create(id) plus fluent extensions (.Named, .Describe, .Run, .Persistent, .ConflictsWith, .ReplaceIfExists, ...) replaces the object-initializer construction across every direct call site (~20 places); only Id and Run have no sensible default. The property Run was renamed RunAction to free up the .Run() extension method (the delegate property otherwise shadowed it). OnStatusChange / OnPercentageChanged extensions are exposed as .WhenStatusChanges / .WhenPercentageChanges for the same reason. 4. Cooperative cancellation helper await args.YieldAsync() now bundles the standard ThrowIfCancellationRequested + WaitWhilePausedAsync pair into a one-liner; the rules doc nudges authors to sprinkle it through tight loops. Stop() also schedules a warning if the task body doesn't observe cancellation within 10s, so a missing yield point shows up in the log instead of as silent latency. 5. Background page reuses TaskTable src/web/src/pages/background-task replaces its bespoke static table with the FloatingAssistant TaskTable (per-row actions, real-time timing, filtering) and adds a separate Schedule panel below for editing interval / enableAfter on persistent tasks. 6. Consolidated status predicates The "is task busy / stoppable / advancing / finished" multi-state checks were copy-pasted across ~10 sites with subtle drift — adding the new transitional states required updating each one manually. Centralizes them on BTaskStatus as IsActive / IsActiveOrPending / CanBeStopped / IsAdvancing / IsFinished, each documented with the question it answers. Call sites now read like prose and stay correct when a new status joins the enum. Plus two dev-only test tasks (gated by IWebHostEnvironment.IsDevelopment; zero production footprint): - DevTestPersistentTask: recurring 1-minute interval, ~60s body, with a deliberately non-yielding 3s section midway so authors can watch the Pausing / Cancelling chips linger while the body is stuck between yield points. - DevTestOneOffTask: one-shot at startup, ~30s body, fully cooperative. Demonstrates the responsive case where pause / resume / stop land within a few hundred ms. Other touch-ups: BTaskManager.Pause/Resume are now async. The "is busy" checks in PathSyncManager, BackgroundTaskController, ComparisonController and BakabaseHost.CheckIfAppCanExitSafely use the new predicates. Frontend locales and useTaskTimingSimulation handle Pausing/Resuming consistently. Three new tests in BTaskTransitionalStatusTests lock down the synchronous status flip. SDK regen committed (constants.ts). Closes #1183 https://claude.ai/code/session_014DmnQqBi52c7pifRoD5CS8
refactor(av-enhancer): dispatch via IAvClient and serialize details d… …irectly Two maintenance traps in AvEnhancer/AvController: a hand-written 20-entry dispatcher dictionary that AvEnhancer rebuilt per call, and a hand-written 20-key dictionary used to dump per-source debug results. Adding or removing a scraper required updating both dispatchers plus the debug shape, and the failure mode was silent — the new source just never got called or its fields disappeared from the dump. Introduce IAvClient (SourceId + lowest-common-denominator SearchAndParseVideo signature). Each of the 20 per-site clients now implements it via explicit interface implementation, preserving their strongly-typed overloads for direct callers. AvEnhancer and AvController inject IEnumerable<IAvClient> and build their dispatcher from SourceId, so adding a source only requires implementing the interface and registering one DI binding. Also replace the hand-written perSourceData dict with JsonSerializer.Serialize(context.Details) — reflection over IAvDetail's public getters captures every field automatically. AvEnhancer no longer needs AiravClient just for its HttpClient; it pulls a default-named client from IHttpClientFactory for cover/poster downloads. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PreviousNext