Repository navigation
Extract Home and Shelf loading from DialogManager into pure loaders - #72
Closed
CaptainCamin wants to merge 213 commits into
Closed
CaptainCamin wants to merge 213 commits into
CaptainCamin wants to merge 213 commits into
Conversation
Feature code survived intact; git history, CI config, and the spec/ harnesses did not. Re-initialised the repository so work can continue.
Both shelf views crashed on device and there was no way to see why. The first attempt loaded the real vendored ListMenu/CoverMenu under stubs, but every KOReader method those widgets touch that the harness did not stub surfaced as a nil call indistinguishable from a plugin bug -- two passes went into chasing those before it was clear the approach could not separate the two. Rewrote it on zlibrary.koplugin's approach instead: stub the KOReader boundary, capture what the plugin hands the real widget constructors, assert on that. The plugin's own modules are never stubbed, so this tests code we actually wrote. spec/support.lua holds the shared scaffolding and the capturing Menu. spec/shelf_dialog_harness.lua drives the real ShelfDialog across both statuses, twelve missing-optional-field variants, six shelf sizes and the paging states. Also restores spec/run_all.sh and CI, both of which were lost with the earlier working tree.
The earlier working-tree loss took most of spec/ with it, leaving only the config and shelf harnesses. These four put the coverage back for the two features that had none. sync_queue_harness (36) -- the queue is the whole of offline sync. Losing a page or replaying a send silently corrupts the user's reading history, and none of it is visible on the device until it is wrong. cache_harness (20) -- the half the queue cannot reach: what the UI shows while offline. Wrong hydration means the user opens a book they have been reading and the plugin reports a page they never turned to. auth_harness (28) -- centred on the one hazard that cannot be recovered from: Hardcover's refresh tokens are single-use, and replaying a spent one revokes the whole chain. Pinned that exactly one refresh is ever presented, that an unknown outcome is never retried, and that sign-out survives a restart. oauth_client_harness (18) -- OAuth reports failures as a 4xx plus a JSON body carrying the real reason. Discarding the body turns 'the user has not approved yet' into an indistinguishable failure, so every sign-in looks broken. Also adds spec/json.lua. A stub that raised on every body would have made the error-code paths untestable, which is precisely where the subtle bugs are. Three harness bugs worth recording, all found by these tests: - readSetting must return the live table by reference, as real LuaSettings does. Returning a copy silently discarded every write, so the queue appeared to lose entries. - HARDCOVER.STATUS.READ does not exist (it is FINISHED), and referencing a missing constant yields nil, which quietly neutered five assertions. Both harnesses now verify the constants they use at load time. - uihttps delivers a response body through the sink callback, not the return value; returning it as a third value left the sink empty and made every 200 look malformed. 151 checks total, from 49.
Both the Want to Read and Currently Reading lists crashed on device. Cause:
hardcover/vendor/listmenu.lua decides how to draw a row with
self.is_directory = not (self.entry.is_file or self.entry.file)
Shelf items set neither field, so every book was rendered through the
directory branch instead of the book branch -- the one that draws a cover and
handles a file entry. search_dialog.lua sets
file = "hardcover-" .. book.book_id
and the shelf dialog simply omitted it, which is why the search list worked
and both shelf lists did not.
Fixed by setting the same synthetic marker in ShelfDialog:createListItem. The
marker is never opened; it exists so the menu classifies the row as a book.
Pinned with two assertions in the shelf harness, which fail against the old
code. Neither book_detail_dialog nor journal_dialog uses the vendored list, so
neither was affected.
Found by comparing against zlibrary.koplugin, whose menu.lua sets show_cover on
an extended Menu and documents the two related crash modes it hit (a no_title
crash in mergeTitleBarIntoLayout, and freed widgets left in item.state). Neither
applies here -- this plugin never sets no_title and never embeds item.state --
but reading their approach is what pointed at the branch selection.
The offline sync, shelf lists and the crash fix shipped in this version but were never written down, so the release notes described only the OAuth work.
Symptom: tapping a dialog item in the plugin menu did nothing, and the screen
only updated after a power cycle.
Cause was not a repaint. Every dialog-opening menu item ran its show from inside
an AutoWifi callback, and AutoWifi has paths that never invoke that callback:
- airplane mode: wifiPrompt returned immediately
- withWifi returned silently when a connection was already pending
- withWifi returned silently when the device cannot restore wifi
- withWifi's restore branch called back only from a connectivity check that
may never fire
On those paths the callback never ran, so the dialog was never constructed. On
e-ink a screen that never changes looks exactly like a stale framebuffer, which
is why this read as a missing-refresh bug, and why a power cycle appeared to fix
it -- the cycle rebuilt the UI and revealed the screen that had been requested
all along.
Two changes:
- HardcoverMenu:withWifiThen runs the action first and layers the wifi prompt on
top, so the screen opens whether or not wifi comes up. A failed fetch now
reports 'Could not load your list from Hardcover' instead of nothing at all.
This is the shape zlibrary.koplugin uses: it shows every dialog directly in the
menu callback and handles connectivity separately.
- AutoWifi:withWifi and wifiPrompt now reach their callback on every path, so
the remaining callers (link book, and the progress paths in main.lua) cannot
hang either.
Pinned by spec/menu_wiring_harness.lua, which fails against the old code on four
separate callback paths plus a source check that no menu item defers its dialog.
The About box and the sign-in flow both blocked on an HTTPS request before calling UIManager:show, so on a device that could not reach the host nothing was ever put on screen. On e-ink a screen that never changes is indistinguishable from one that failed to refresh, which is why this was twice mistaken for a repaint problem. About was the clearer case: it called Github:newestRelease() first, and that request had no timeout at all -- unlike hardcover_api and oauth_client, which both set one. With no route to api.github.com it blocked until TCP gave up, and the About box was downstream of it. - github.lua: a 5s timeout, pcall around the request and the decode, and a newestReleaseAsync() so callers need not block. - About: builds the text, shows the box, then fills in the release note if GitHub answers. Its appearance no longer depends on the network. - signIn: shows a 'Contacting Hardcover' indicator before beginDeviceFlow, and closes it before showing either the code dialog or an error. Also fixes a double-show: main.lua called UIManager:show(dialog) and then dialog:onShowSignIn(), which shows it again. UIManager has no duplicate guard, so one screen got two window-stack entries. spec/no_blocking_menu_harness.lua pins the rule. Verified it fails against the pre-fix code by reverting the About entry and the timeout in a scratch copy -- 2 of its 7 checks go red -- because a guard that cannot fail proves nothing. The earlier commit's wifi changes are unrelated to this and stay: those callbacks genuinely never fired on four paths, which was a real bug, just not the one being reported.
The dialog had never been rendered against real KOReader widgets, and it did not open at all. Four separate faults, each of which a stub harness cannot see because the widget never runs: * button_row.height is nil. HorizontalGroup sizes itself behind getSize() and has no .height field, so the ScrollableContainer height arithmetic raised "attempt to perform arithmetic on field 'height' (a nil value)". * ScrollableContainer was given width/height, which it ignores: it takes its size from an explicit dimen, and reads dimen.w/h in initState. The first paint died with "attempt to index field 'dimen' (a nil value)". * button_row was appended to the scroll's content *and* placed outside it, so the same widget had two parents and the Close button rendered twice. * Shelf.detailRows emits the description as a metadata row, and the dialog also gives it a dedicated wrapping box, so the whole paragraph printed twice. Filtered at the display layer so detailRows stays complete. Emulator harness, extended so a change can be checked before it is ported: * spec/emu/tree.lua walks the painted widget tree and returns visible text with geometry. Screenshot-independent, and the reason the duplicated description was catchable at all -- two identical paragraphs read as a rendering artefact in a PNG but not as a count. * Each shot also writes a .txt of everything drawn. Reading a PNG needs a vision model, which may be rate-limited or absent; the text dump is always there, and two runs that render differently diff as text. * Scenarios for shelf, search, book detail and journal. They assert on structure and content, not just "it rendered": the file-marker invariant on every list row, no text drawn twice, nothing outside the panel, paging that actually advances. * boot.lua gains key dispatch and a widget-tree dump. Both had traps worth recording in the README: UIManager:sendEvent returns nothing even when the press worked, and key bindings are device keycodes (RPgFwd), so "NextPage" matches nothing and is dropped silently. Fixtures use the plugin's real settings and dialogs and replace only the network. A scenario that depends on an unstubbed call shows up as an empty screen rather than a false pass. spec/emu/.home and .out are gitignored; package_release.sh already excludes spec/ entirely, so none of this ships. Both suites pass: 4 emulator scenarios, 36 stock-Lua checks.
boot.lua defined emu:key twice: an UIManager-dispatch variant and a topmost-widget variant that swallows whether the key was consumed. The later definition won, so every scenario silently got the lenient one -- a key matching no binding passed instead of failing, which is the same silence that makes a broken widget look like a no-op. No scenario called emu:key (only the README), so nothing depended on the overriding behaviour. The lenient variant is now emu:keyLenient and both survive; emu:press remains the one that asserts consumption. Also gave the surviving emu:key a comment saying what it does *not* check, since UIManager:sendEvent returns nothing about handling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The plugin had no shared way to say "loading", "that failed" or "try
again", so every screen improvised. That is how a failed rating save came
to be reported by calling a misspelled field (hardcover_menu.lua:716,
dialog_magager), which raises on nil instead of reporting anything.
Adapted from zlibrary.koplugin's ui.lua and dialog_manager.lua. Three
things carried over deliberately rather than reinvented:
* No force_one_line on the loading message. To fit one line InfoMessage
shrinks its font and re-runs init(); a plugin that patches
InfoMessage.init (appearance.koplugin does) reassigns the font every
call, so the loop never converges and KOReader dies with a stack
overflow. Wrapping to a second line needs no re-run. Asserted absent,
because it is invisible until it kills a device and adding it is the
natural thing to do when a message does not fit.
* The retry frame is impersonal -- "Could not complete X" -- so the
operation name is an object and the verb has nothing to disagree with.
Naming it as the subject broke ten of z-library's fourteen locales
("Kommentare ist fehlgeschlagen").
* The error path carries icon = "notice-warning". Without it every error
rendered as an ordinary notice while the code claimed otherwise.
The retry text includes the underlying error and never claims the failure
was temporary: for a walled server or a wrong host it is not, and
"temporary" invites a retry that cannot work.
Not yet wired into any screen -- Phase 2 converts the blocking
dialog_manager paths to show-then-fetch and uses this.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
spec/no_blocking_menu_harness.lua already pinned two menu paths (About,
sign-in). Both pass. The dialog paths were never covered, and all four
of them block on the network before showing anything:
showShelf API at 1, show at 2
showBookDetail API at 1, show at 2
updateSearchResults API call, no show at all
journalEntryForm API at 1, show at 2
With no route to the API that is up to six seconds of nothing after the
tap (socketutil:set_timeout(6, 12) at hardcover_api.lua:176), and on
e-ink the panel keeps whatever was last painted until the user
power-cycles. That reads as a crashed device.
The harness records UIManager:show and the API call on one shared
timeline and asserts the show came first, so a regression that moves a
fetch back above a show fails here rather than on a device.
Committed RED on purpose: this asserts behaviour the plugin does not yet
have, so run_all.sh reports FAILURES until the dialog_manager paths are
converted. The four diagnostics above are the bug, stated numerically.
Two notes on what this harness deliberately does not do:
* It never calls a dialog's init. Building a widget tree under stubs
means every unstubbed KOReader method surfaces as a nil call that looks
exactly like a plugin bug. The constructor spec is captured instead,
which is what answers the ordering question. A null-object fallback
covers the callers that immediately poke a field init would have built
(dialog_manager.lua:238 scrolls _input_widget unguarded).
* The cost of that choice is real: a dialog missing a field it should
have set is invisible here. spec/emu/run.sh is what covers that, by
rendering through the real frontend.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three stacked bugs, all silent until a scenario actually tapped something:
* ui/gesturedetector does not exist; the module is device/gesturedetector.
* There is no Gesture class to instantiate. gesturedetector builds a plain
table -- { ges = "tap", pos = Geom:new{...}, time = ... } -- and ges_events
match on the *string* via `gs.ges ~= self.ges`. Constructing anything else
yields a gesture every widget ignores.
* ui/time is fixed-point. Time:s(x) builds a time from a count of seconds, so
Time:s() is arithmetic on nil; Time.now() reads the clock.
It also referenced an undefined global `Event` instead of self.Event.
The old body swallowed all of this in a pcall and returned nothing, so a broken
tap looked exactly like a tap nothing responded to. tap() now returns whether a
widget consumed the gesture, and tapExpecting() asserts on it -- a tap outside
every registered range is dropped silently, which is otherwise
indistinguishable from a widget ignoring touches.
Verified: a tap at a row selects it and fires the selection callback; taps on
empty space and the title bar correctly return nil.
Also removed a duplicated emu:key definition. In Lua the later one wins, so the
two variants had been shadowing each other.
Four dialog entry points fetched before showing anything:
showShelf Api:getShelf -> shown 30 lines later
showBookDetail Api:getBookDetail -> shown after
updateSearchResults Api:findBooks -> no show, error was a no-op
journalEntryForm Api:findDefaultEdition-> dialog built after the lookup
With no route to the API that is up to six seconds of nothing after the
tap (socketutil:set_timeout(6, 12)), and on e-ink the panel keeps
whatever was last painted until the user power-cycles -- which reads as
a crashed device, not as a network problem.
Each now builds its dialog and shows it first, then fills it in from the
callback. The async wrappers on HardcoverApi deliver through
UIManager:nextTick so a caller may touch widgets directly; every write
is guarded on isWidgetShown because the user can close the dialog while
the request is in flight, and updating a freed widget crashes.
Also in this change:
* BookDetailDialog grows a loading body and setDetail. setDetail re-runs
init, which is safe here because every widget in the body is a pure
function of self.detail -- noted in the code, because the same trick
would destroy FocusManager focus if used for live text.
* ShelfDialog grows setEntries, setEmptyState and updatePager. An empty
shelf previously rendered as a title bar over a blank list, which
reads as a bug rather than as an answer. The empty-state row still
carries a `file` marker: without one the vendored ListMenu draws it
through the folder branch that already crashed this plugin once.
* updateSearchResults no longer swallows failures. It closed the dialog
only when Api.enabled was false and otherwise left stale rows that
looked like results. It keeps the old rows on failure too -- clearing
them turns a transient error into "no matches", a different wrong
answer to the question asked.
* Two silent write failures in hardcover_menu.lua. A failed rating save
called self.dialog_magager (misspelled), so it raised on nil instead of
reporting -- and raised after the spinner was gone, so the screen just
did not change. A failed page write hit an empty else branch.
journalEntryForm's edition lookup moved after the show rather than being
dropped: the dialog is usable without it, and setEdition fills it in.
Verified: spec/run_all.sh OK (the red harness from fc11656 is now 4/4),
and all four emulator scenarios still render through real KOReader.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous commit converted the dialog_manager entry points. These
four lived in callers and blocked the same way:
Change edition Api:findEditions in the menu callback
Link book findBookOptions -- two round trips, before any
dialog existed
Suggest a book cacheRandomBooks on a cold cache
Journal -> edition Api:findEditions inline in select_edition_callback
New: DialogManager:buildLoadingSearchDialog. Takes a fetch function
instead of a list, shows an empty dialog first, then fills it. "Change
edition", the link-book dialog and the journal's edition picker all route
through it, so the three copies of fetch-then-show collapse into one.
buildBookListDialog grew the same optional fetch, because its rows hand
off to the file searcher rather than to linkBook -- reusing the search
dialog's own select callback would have been wrong.
SearchDialog grows setEmptyState. Its "No items" was indistinguishable
from a list that failed to load, and both the edition picker and the
suggestion list can genuinely come back empty.
Suggest a book still prefers its cache -- a warm list means no request
at all. Only a cold cache goes to the network, and then the dialog is
already up. The empty case no longer raises a Notification and returns
without showing anything; it is an empty state inside a real screen.
Kept buildSearchDialog: spec/emu/scenarios/search.lua drives the search
screen through it, so it is a test entry point as much as a production
one. Verified rather than assumed -- grepped before deciding.
Also resolved the journal re-entrancy TODO at :207 by explaining it in
place. buildLoadingSearchDialog assigns self.search_dialog while a
journal dialog is up, which is safe because the journal dialog is a
local rather than a field, and the two are different widget classes.
Harness now 6/6, suite OK, all four emulator scenarios still render.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every shelf row rendered as:
The Lathe of Heaven
hardcover-201
Want to Read
The marker is the synthetic `file` value that stops ListMenu drawing an
API-sourced row as a folder, and it was reaching the screen as the book's
name. vendored/listmenu.lua:277 sets bookinfo = self.entry, then :502
renders title = bookinfo.title or filename_without_suffix, and
filename_without_suffix is parsed back out of entry.file (:131). Shelf
rows set `text` but no `title`, so every one of them fell through to the
filename branch.
A stub harness cannot see this: the item table is correct in the broken
case too, with a `file` and a `mandatory` exactly as it should be. It
surfaced only by reading spec/emu/.out/shelf_page1.txt.
Row shaping moves into hardcover/lib/ui/list_row.lua, shared by both
list dialogs, which had drifted into two different implementations. It is
pure -- no widgets -- so it runs under stock Lua. Three row fields are
load-bearing and each now has a test:
* `file` -- without it the row renders as a folder. Crashed both shelf
views on device once already.
* `title` -- without it the marker becomes the visible name. The bug
this fixes.
* cover keys -- absent when there is no cover, never empty. An empty
cover_url builds an image widget that can never load.
Four defects fixed in the move:
* `if book.contributions.author` indexed a FUNCTION, so the primary
author was always dropped and only contributors survived.
* `series` was assigned three times; the last write won, so a series
position was discarded whenever the book also had a language.
* The language branch read `self.series` -- the dialog's field, never
set -- to decide whether to prefix, so it always took the wrong
branch.
* Language was carried in the `series` field, conflating two things and
making ListMenu render a language as a series name.
Two things the existing tests caught that are worth recording:
* Shelf rows arrive with `authors` already joined into a STRING by
Shelf.normalizeEntry, while search rows carry author.name plus a
contributions list. Handling only the structured shape silently dropped
the shelf's whole author column. Both shapes are handled now.
* The shelf harness asserted the author against `text`, which was only
true because the old code concatenated authors into the text string.
The author lives in `authors` now, which is what ListMenu and SearchMenu
actually paint from; concatenating it into `text` printed the title
twice on the SearchMenu path.
The emulator scenario now asserts the marker is absent from the painted
text, and that guard was verified to fail when `title` is removed -- a
check that cannot fail is not a check.
Suite OK, emulator OK, and the shelf rows now read title, author,
status and pages.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two tooling gaps, both of which had been hiding real risk. run_all.sh was testing on whatever `lua` was on PATH. On this machine that is 5.5; KOReader is LuaJIT (5.1 semantics) and CI pins 5.1. Code that runs green on 5.5 can still be a syntax error on the device, so the suite was proving nothing about the target. It now prefers luajit, then lua5.1, and prints a warning when neither is present rather than quietly testing on the wrong one. It also reported a bare "FAILURES" with no [FAIL] line anywhere when a harness exited non-zero without printing one -- which is how the shelf_dialog breakage after the status_dialogs change presented, and it cost a loop over harnesses one at a time to diagnose. Output is now captured per harness and the summary names every failure. Verified by adding a deliberate failing assertion and confirming it appears in the list. package_release.sh gets artifact verification, which it had none of. It checked that zip was TOLD to include _meta.lua and main.lua; it never checked that the built bytes were right. Now it extracts the archive and confirms: the top-level directory exists (without it KOReader installs nothing), no credential file, no dev directories, the shipped tree matches the stage, the stage matches the working tree, every .lua parses, and every internal require resolves inside the archive. Three real bugs in that script, each found by running it rather than reading it: * It ran `zip -r` from OUT_DIR expecting the plugin directory to be there. Pointed anywhere else -- a staging area, /tmp -- zip found no input and exited 12 with NO output. Now stages an explicit copy of what ships, which also makes the dev-directory check meaningful instead of relying on a -x list that drifts. * ROOT was never assigned; the staging code referenced it and died on `set -u`. * The require check stripped a "hardcover/" prefix from paths that already included it, pointing every lookup a directory too deep. It reported all 38 modules as missing. The token guard covered only hc_pat_. The plugin ships OAuth, so a real access or refresh token starts hc_at_ or hc_rt_ and would have shipped past it. All three prefixes are checked now, and the reported matches are shown so a human can tell a real token from a spec placeholder. Verified by pasting a token-shaped string into a source file and confirming the build refuses and names it; the credential-file guard was verified the same way. Also corrected a comment claiming this script is shared with .github/workflows/release.yml. There is no release workflow -- workflows/ holds test.yml alone -- so this script is currently the only definition of what ships. The comment now says that, and says a future release workflow must call this script rather than zip anything itself. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There were three ways to show an error in this plugin, all building an InfoMessage by hand: DialogManager:showError 2 second timeout BookDetailDialog:showError no timeout at all StatusDialogs.error 5 second timeout, warning icon All three now delegate to StatusDialogs.error. The divergence was not cosmetic: BookDetailDialog's version had no timeout, so a failure shown from there had no defined lifetime, and the three different timeouts meant no two screens behaved alike when reporting the same kind of fault. Also pinned the shelf rating formatting, which the row extraction could plausibly have broken: a whole rating reads "4*" not "4.0*", and a fractional one keeps its decimal. Both are now asserted, since that formatting is user-visible and previously untested. Left the success Notification in hardcover.lua alone -- that is a different thing from an error and is not StatusDialogs' business. Suite OK under LuaJIT, emulator OK, marker leak still absent from the painted shelf text, and the release zip re-verified after the change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, repaint before sign-in request Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
…rite once per enqueue Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
…test order-proof Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
…n unknown outcome Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
…dialogs instead of leaving them on the stack Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
…freeze the UI; grey out Sync now when nothing is pending Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Similar to [book] carousel on the details screen (1.3.1)
…aps) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Series strip above "Similar to" (1.3.2)
…ersion 1.3.3 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Similar strip: load after the series, retry when cut off, say when it fails (1.3.3)
…ve author; version 1.3.4 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Strips ignore touches, long Similar heading, title above author (1.3.4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Swipeable strips and an On device search button (1.4.0)
… an error; version 1.4.1 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
…tly Reading shows offline reading Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
…ed with their tag Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
…nd workflow tag handling fixes Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
1.4.1: offline manual page/rating, one Hardcover button, offline Home reading, beta updates, similar-strip placeholder
…ed in place; version 1.4.2 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
…gs keep the book's title
…copy, Home tile, screen, setting); version 1.5.0 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
…ns, Based on...) behind a Vibes tile; asks for read:vibes Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
… a status keeps the page's question, the username lookup retries Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS
1.5.0: For you, Vibes, pending changes, offline fixes, one Hardcover button, beta updates, account name
* Add e-ink chart widgets (columns, bars, donut, KPI tiles) and their pure geometry Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS * Stats data layer: pure stats computation, paged finished-books query, saved copy Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS * Stats screen: charts of your reading, period chooser, saved copy, Home tile Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS * Book details: community rating breakdown and genre, mood and content-warning labels Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS * Stats: ask for the genre counts only on the first page Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS * 1.6.0: Stats, and what readers say on a book's details Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS --------- Co-authored-by: Claude <noreply@anthropic.com>
… without an edition (#38) * Move the ratings breakdown from the book details to the Reviews screen Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS * Book details: release dates, audiobook length, credits by role, review, list and edition counts Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS * Book details: ask for the community and detail fields in the book's own request too, not only the edition's Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS * 1.6.1: details rows, ratings breakdown on Reviews, fix for books without an edition Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XoUz2Ej6GE9ammm1V8yFYS --------- Co-authored-by: Claude <noreply@anthropic.com>
showHome and showShelf mixed building the dialog with the fetch, cache and retry logic, which could only be tested through a UI harness. The paging loop (retries, 429 backoff, de-duplication, page cap) is now ShelfLoader and the Home refresh sequence is HomeLoader. Both take the API, network check and liveness test as arguments, so they run under stock Lua and have their own harnesses. DialogManager now builds the dialog and says what to do with each result. Behaviour is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
|
Opened here by mistake (wrong target repo); closing. |
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.
What changed
hardcover/lib/shelf_loader.lua: the whole-shelf paging loop (page size, retry on a cancelled request, 429 backoff, de-duplication, page cap) plusplan(), which decides what the screen does with the outcome.hardcover/lib/home_loader.lua: the Home refresh sequence (counts, reading, lists, goals), which saves what comes back and reports only what changed.DialogManager:showHome/showShelfnow build the dialog and handle each result; they went from 2,132 to 2,036 lines.spec/shelf_loader_harness.lua(18 checks) andspec/home_loader_harness.lua(10 checks).Why
DialogManagermixed UI building with fetching, caching and retry rules, so that logic could only be tested through a UI harness. The loaders take the API, network check and liveness test as arguments, so they run under stock Lua like the other pure modules. This is the first step of splitting upDialogManager.Notes for review
offline_shelf,close_refresh,no_blocking_dialog,menu_wiringandstartup_weightharnesses pass.spec/charts_harness.luafails when run on Lua 5.5 (math.atan2is gone); it fails the same way on main. Other harnesses needunpackshimmed on 5.5. Not run on LuaJIT or in the emulator.🤖 Generated with Claude Code