Skip to content

Release 1.8.0: staff area with cataloguing, circulation and plugin content, offline catalogue and a loans calendar - #47

Open
fabiodalez-dev wants to merge 50 commits into
mainfrom
feat/staff-catalog-offline-calendar
Open

fabiodalez-dev wants to merge 50 commits into
mainfrom
feat/staff-catalog-offline-calendar

Conversation

@fabiodalez-dev

@fabiodalez-dev fabiodalez-dev commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

What this adds

Pinakes Android 1.8.0 (versionCode 21) brings a native staff area for admin and staff accounts, an offline catalogue and a subscribable loans calendar. Each staff section appears only when the signed-in role is admin or staff and the server advertises it (Mobile API 1.7.0, fabiodalez-dev/Pinakes#477); on older servers the entries stay hidden and the app behaves as before.

Staff area

  • Cataloguing: ISBN/EAN scanner (CameraX + ZXing, no Google Play services) with checksum validation, double-read confirmation, batch mode and preview/duplicate/not-found sheets; manual records for items without an ISBN; full book editor with copies (own inventory codes, status, notes), shelf location, cover from URL/photo/gallery, label PDFs; authors and publishers with detail pages; genres; shelves; series; merge duplicates; CSV and LibraryThing import with resumable progress; missing covers and enrichment; digital file upload.
  • Circulation desk: counters, loans by status with search, loan detail with every action the server offers (approve, reject, pickup, return with copy outcome and penalty, renew, edit, reminder, receipt PDF/email), new loan with borrower search, book search or a scanned copy label and a per-user availability calendar, reservations queue, return by scanning a copy label (CODE 128 / CODE 39 / EAN-13), bulk extend and reminders. Creates send an Idempotency-Key, so a retry never lends a second copy. The whole section is hidden offline.
  • Plugin content: donation offers (Desiderata), archives with authorities and documents, periodicals desk (issue lookup, receive, claim, years, issues, subscriptions), book clubs (settings, workflow, members), FRBR works and expressions.

Offline catalogue

Room mirror of the whole catalogue kept current by /catalog/sync, plus the user's loans, reservations and wishlist. Search, book pages, My Library and Wishlist work offline; loan and reservation actions are hidden (not disabled) while offline and cached availability is shown as "last known".

Loans calendar

Profile → "Loans in your calendar": a private link with loans (start to due date) and reservations, copied as a sensitive clip and shown masked, with webcal opening, regenerate and turn off.

Other

  • Gender and tax code disappear when the library hides them.
  • The app sends its language (Accept-Language), so server labels and messages follow it; dates follow the language picked in the app.
  • The loan calendar fits phone screens; tab labels never wrap mid-word; raw network errors are never shown.
  • README gains a staff area gallery (24 screenshots).

Tests

  • 380 JVM unit tests, 0 lint errors, debug and R8 release builds.
  • 92 instrumented tests on an Android 15 emulator against a fake on-device server with fixtures, covering every staff flow, offline mode, the calendar sheet and phone-width layouts.
  • tools/e2e/live-smoke.sh drives the real app against a real Pinakes instance (19 steps, cleanup of everything it creates); STATUS.md explains how to run both suites.

Fixes #39
Fixes #32
Fixes #33

CameraX 1.4.1 (camera2, lifecycle, view) drives the preview and frame analysis; ZXing core 3.5.3 decodes EAN-13 in pure Java, so scanning needs no Google Play services.
…calendar

Mobile API 1.6.0 support without any UI change yet: the staff_catalog, catalog_sync and calendar_feed health flags (off on older servers); the user role kept in SessionStore from login and GET /me, with StaffAccess deciding who sees the staff area; StaffApi and StaffRepository for every /staff route (a 403 drops the stored role); tolerant models for lookups, ISBN lookups, books, authors, publishers, genres and shelves; 409 duplicate and conflict context kept in ApiResult.Failure.meta.

Pure, unit-tested logic: IsbnCodec (EAN-13 and ISBN-10 checksums, ISBN-10 to 13), ScanDebouncer (two identical reads, 3 s suppression, pause), StaffBookForm with StaffBookWrite (create body, true patch for edits that re-sends preserved contributors), SyncMerge (last change wins, full resync, weekly forced pass) and CalendarLinks.

Room schema v2 replaces the 40-row snapshot with the whole catalogue plus cached loans, reservations, wishlist and sync bookkeeping. CatalogRepository.syncCatalog() pages through GET /catalog/sync, applies the result in one transaction and only then stores the watermark; servers without the endpoint keep the old snapshot. Loans and wishlist are written through on every fetch and purged on sign-out or a new sign-in. The sync worker refreshes the personal lists too, the cache database is excluded from backups and the foreground observer also refreshes the role.
Search falls back to the cached catalogue (title, author, publisher and identifier match, the same facets by value, offset paging, title or newest order) under an offline banner. A book page is built from the cache with a notice; Library and Wishlist show the last lists the server sent.

Availability cannot be guaranteed offline, so loan and reservation affordances are hidden, never disabled: no request or reserve button, no loan calendar, no cancel actions, no wishlist toggle or reviews. Cached availability is never shown as current: the book page says when it was last known (sync time), status pills are hidden on cached cards and the cached Home shelf is labelled as recently added instead of available now. OfflinePolicy holds the decision and is unit-tested.
Profile shows "Loans in your calendar" when the server advertises calendar_feed and loans are on. The sheet creates the private ICS link, leads with Copy link (Google Calendar on Android cannot subscribe from the device; the hint explains the website route), opens it as webcal:// with the HTTPS link as fallback, and offers Regenerate and Turn off, both behind a confirmation because existing subscriptions stop updating.

Profile also shows "Staff area" for admin and staff users on servers with staff_catalog, and GET /me now refreshes the stored role.
Staff hub (Profile, or the scan button on the Catalog tab) for admin and staff users on servers with staff_catalog. Books: searchable list of every record with edit and delete (refused with a clear message while loans or reservations are active). One create/edit form serves scanned titles and records without an ISBN: live identifier validation, author and publisher pickers with add-new, genre and subgenre, media type, language/format/acquisition suggestions from the lookups, shelf and level with the next free position, copies (create) or add copies (edit), and a cover from a URL, a photo or a picked image (resized, EXIF-rotated JPEG under 2 MB, uploaded after the save). A 409 duplicate offers Open existing, Add copy or Change identifier. Authors, publishers, genres (tree, find-or-create) and shelves with levels have their own screens.

Scanner: CameraX preview with a viewfinder, ZXing EAN-13 decoding on alternate rotated frames, checksum validation and the double-read debouncer. Batch mode remembers copies, shelf, level and an opt-in auto-confirm; preview, duplicate and not-found sheets lead to add, add copies, edit details or create manually. The manual ISBN/EAN field is always visible for emulators and denied permission. CAMERA is requested only here, with a rationale and a settings fallback when it is permanently denied; camera.any is optional so devices without a camera still install the app. Without a connection the scanner says that scanning needs one.
…oans calendar

Bump the version and record the new features, their server requirement (Mobile API 1.6.0) and what is not yet verified on a device in STATUS.md and the Uwe parity notes.
The feed URL carries a bearer-equivalent token. Copy link now flags the clip as sensitive (ClipDescription.EXTRA_IS_SENSITIVE on Android 13+, the same extra key before) with a neutral label, so the clipboard preview and keyboards do not show or keep it, and the sheet shows only the host and the last six characters instead of the full URL. The URL is never logged.
Blank fields became null and the shared Json drops null keys, so the server kept the old value and a pseudonym, date, website or GND id could never be emptied. Every editable field is now always sent, trimmed, with a cleared field as an empty string, which the server stores as empty (dates and the GND id as NULL). RecordWritesTest checks the encoded bodies.
A search or pull-to-refresh during a page load dropped that page through the generation check but left loadingMore set, so loadMore() never ran again. reload() now clears the flag.
Picking a shelf without a level sent null shelf and level ids, which cleared the stored location without a word. A shelf without a level is now a validation error with a message under the level field (all four languages); nulls are sent only when the shelf is set to none, and a stored location without a level is left untouched instead of making the form invalid.

When the record saved but the cover upload failed, the screen navigated away and the error was lost. It now stays on the saved record (switching a create into an edit of the new id), keeps the pending cover so the next Save retries it, and says so.
Search: rows store a folded search key (NFD, combining marks removed, lower case; identifiers without dashes or spaces) and queries are folded the same way, so Città, Über or ÆRØ match regardless of accents and case. LIKE metacharacters in the query and the author/publisher filters are escaped with ESCAPE, and identifier-like queries drop their separators. Room schema v3 (destructive, refilled by the next full sync).

Sync: a page with has_more and no cursor now fails the run instead of applying a partial result and advancing the watermark. Clearing the cache (forget instance) bumps a generation under a write lock that every sync write also takes, so a run in flight can no longer write the old library's rows or watermark after the clear. The book detail ETag cache is concurrent.
Any 403 counted as a revoked role, so a WAF or proxy block wiped the stored role and closed the form. ApiResult.Failure now records whether its code came from the server's envelope, and only an envelope error.code of forbidden drops the role.
A ConnectivityObserver (system default-network callback, Hilt singleton) now feeds Home, Search, book detail, Library and Wishlist. When the device goes offline, screens already loaded online hide availability pills, the available-now claim, loan and reservation buttons, the loan calendar, cancel and remove actions at once; when it comes back they reload and the offline catalogue syncs. Unit tests cover each screen state.

A NETWORK failure never reaches the UI as its IOException text: resolvedMessage() and the new uiText()/serverMessage() helpers give a localized offline message, Library and Wishlist without a saved copy show an offline empty state, Home shows the same wording, staff screens map their load errors likewise, and the desiderata, archive and article sections of Catalog search are hidden when they fail only for lack of a connection. The author, publisher and genre pickers and lists get their own search placeholders.
Counts read "Copies: 1" style in every language instead of a plural noun after the number ("1 copies"): batch copies, added this session, copies added, available of total and the offline last-known line. The calendar sheet now says the feed carries loans (due dates, books ready for pickup, booked loans), not reservations.
The header kept the role of the profile loaded when the screen opened, so a downgraded admin still read Admin while the staff entries had already gone. It now shows the stored role, refreshed by every GET /me, and Profile re-reads GET /me quietly when it comes back to the foreground.
A staff create runs the whole admin pipeline (scraper hooks, cover download) and took over 30 s on a loaded server during the live smoke test: the app timed out and reported a failure for a record the server had already saved. The read timeout is now 60 s; the connect timeout is unchanged, so an unreachable server is still detected quickly.
The new staffe2e suite drives the real app (MainActivity, real Hilt graph, ViewModels, repositories and Room) against FakePinakes, a MockWebServer on the device loopback that answers JSON fixtures from androidTest/assets/fixtures and records every request. UiAutomator handles the system camera prompt; airplane mode is real.

Covered: staff entry only for admin/staff with staff_catalog (hidden for standard users, without the flag and after a server-side downgrade, header included); scanner manual ISBN to preview to add with the counter, duplicate to add copies, not found to the manual form, invalid code rejected without a lookup, camera denied keeps manual entry; manual form with a missing title or a shelf without a level sends nothing, an edit sends only the changed key; offline search banner with cached results and no availability or loan actions, offline detail with the last-known wording and no loan buttons, Library and Wishlist offline empty state without raw exception text, going offline after an online load hides the loan button and reconnecting restores it; calendar link shown masked, copied as sensitive, regenerate and turn off both confirm. 18 tests, all green on Android 15 together with the 22 existing ones.
tools/e2e/live-smoke.sh drives the installed app on an emulator with adb and uiautomator (tools/e2e/ui.py) like a user: onboarding, sign-in, staff hub, scan-add of an ISBN with two copies, a copy through the duplicate sheet, a record without ISBN, its shelf and level on edit (checking that nothing else changed), airplane-mode search and detail, the calendar link and its feed. Every step is checked through the Mobile API, and cleanup removes only what the run created (books, a calendar link that did not exist before, device tokens issued during the run) and clears the app. Instance URL and credentials come only from PINAKES_E2E_* variables; texts follow the device language through i18n/*.json. STATUS.md documents how to run it and the device tests.
The administrator can now hide the gender and tax code fields. The app reads it from GET /me (hidden_fields, the keys themselves omitted), from the registration schema (profile_fields.*.visible) and from /health (registration.show_*): a hidden field is not rendered in the profile editor and never sent in PATCH /me. Servers that send none of these keep both fields, as before. Registration never asked for them and still does not. Profile re-reads the schema with /me when it comes back to the foreground.

Tests: ProfileFieldVisibilityTest for the decision and the payloads; ProfileFieldsFlowTest on the device for hidden and shown fields, registration, and the birth-date picker fitting a 360dp screen.
Staff Authors and Publishers rows now open a detail page: a padded info card (books in the catalogue, life dates, nationality, website, GND id or the publisher's contacts, and created/updated when the server sends them) and the record's books from GET /staff/books?author_id= (publishers by name), drawn with the whole-cover book cards. Editing moved to the page's edit action. The catalogue filtered by an author gets the same kind of card above the results. The pending cover preview in the book form and the covers in My reviews now fit instead of cropping.
The loan request used Material's DatePickerDialog, which is a fixed 360dp wide with a capped height, and day cells were a fixed 40dp: on 320-360dp phones the month was clipped and cells ran into each other. The request is now a plain dialog sized to the screen (16dp margins, at most 420dp, scrollable), day cells fill their column (at most 44dp), weekday headers share the grid's spacing, and the legend wraps. When availability cannot be loaded the same grid lets the user pick any future day instead of a second date picker. CalendarLayoutTest checks the 7 weekday columns, that every day cell stays inside the screen and inside a 320dp column, and month navigation.
With database access configured, the smoke test hides gender and tax code in the settings table, waits for /health to report it, checks that /me omits them and that the app's profile editor no longer shows them, then restores the previous values. STATUS.md lists the new device tests.
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1828c47a-a48c-4e7e-88cf-798221214164
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Two issues should be fixed before release. The author and publisher editor can wipe a record after a failed load. The search screen gains a new connectivity collector every time a search is submitted, the sort changes or filters are cleared. The other inline notes are smaller.

Reviewed changes

This review covers the whole 1.7.0 release branch: the data layer, staff area, scanner, offline catalogue, calendar and the screen changes. Test fixtures were skimmed.

  • Staff area: adds role-gated catalogue management (books, authors, publishers, genres, shelves). Book edits send patch bodies, and a role is revoked only on the server's own forbidden code.
  • ISBN scanner: adds CameraX and ZXing EAN-13 scanning. A code needs two identical reads, then a 3 s suppression window applies. Includes batch mode and preview, duplicate and not-found sheets.
  • Offline catalogue: moves Room to a v3 schema. The delta sync on /catalog/sync uses a guarded watermark, falls back to the legacy snapshot on servers without the endpoint, and serves offline search with a folded search key. Loans and wishlist are cached too, and loan actions are hidden offline through ConnectivityObserver.
  • Loans calendar: adds create, rotate and revoke for the private ICS feed. The link is shown masked and copied as a sensitive clip.
  • Misc: hides profile fields the library hides, raises the read timeout to 60 s, bumps the version to 20 / 1.7.0, and adds device tests and the live smoke script.

The sync path holds up. The watermark is stored only after a successful apply, cacheGeneration stops a run still in flight when the instance is forgotten from writing back, and LIKE input is escaped. A few lower-confidence leftovers are not flagged inline: cached_books.isWanted is per-user and survives logout, and the cached wishlist and loans only refresh on a full list fetch, not after add or remove.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread app/src/main/java/com/pinakes/app/ui/screens/staff/StaffRecordsScreens.kt Outdated
Comment thread app/src/main/java/com/pinakes/app/ui/screens/search/SearchViewModel.kt Outdated
Comment thread app/src/main/java/com/pinakes/app/ui/screens/search/SearchViewModel.kt Outdated
Comment thread app/src/main/java/com/pinakes/app/ui/screens/search/SearchViewModel.kt Outdated
Comment thread app/src/main/java/com/pinakes/app/ui/screens/profile/ProfileViewModel.kt Outdated
Comment thread app/src/main/java/com/pinakes/app/ui/screens/staff/StaffBookEditViewModel.kt Outdated
Comment thread app/src/main/java/com/pinakes/app/ui/screens/staff/scan/StaffScanViewModel.kt Outdated
When GET /staff/authors/{id} or /staff/publishers/{id} failed, the editor kept a blank placeholder with Save enabled; since saves send every field and an empty string clears it, saving wiped the stored record. The placeholder is now marked as not loaded: it shows the error with Retry only (no fields, no Save), edits and submit ignore it, and a late answer for an editor already closed is dropped.
submitSearch(), setSort() and clearFilters() each started another connectivity collector that was never cancelled, so one reconnect re-ran the search once per earlier interaction. Only the collector started in init remains. ConnectivityCollectorTest checks that a single collector is left after repeated searches, sorts and filter resets.
Creating, regenerating or turning off the calendar link, saving the profile and changing the password showed the IOException text when offline. They now use the server's own message or the localized offline wording.
"Add copy" sent the form's copy count, which in edit mode is the edited record's own total: an ISBN change that collided with another book added that many copies to it. Editing now adds exactly one; creating keeps the copies the user asked for.
The 3 s window started when a code was accepted, but the lookup and the open sheet usually last longer, so after Skip or Add the book still under the camera reopened the same sheet. resume(now) restarts the window for the last accepted code; other codes are not held back.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The new circulation desk has two list bugs to fix before release: loan paging stops after a refresh overlaps a load-more, and the reservations list ends after the first 50 rows. The version bump also no longer matches the PR and STATUS.md. Smaller notes are inline.

Reviewed changes

This review covers the two commits added since the last pullfrog review at dceb7c1.

  • Staff data layer for Mobile API 1.7.0: added StaffCirculationApi, StaffContentApi and StaffPluginsApi with their repositories, all routed through staffCall. Added a long-timeout client derived from the guarded okHttpClient, 14 new staff_* feature flags in FeatureStore/StaffAreas, plus LoanActions, CopyCodeCodec and StaffErrors.
  • Circulation desk UI: added a hub, the loan buckets with bulk extend and recall, loan create and detail, a reservations queue editor and a borrower page. Every screen is gated on staff_circulation and leaves when the area disappears.
  • Copy scanning and exports: added StaffCopyScanScreen, which uses the existing ScanDebouncer with Code128/Code39, while the ISBN scanner stays EAN-13 only. Added StaffFilesRepository, which downloads exports into cache/exports/ (shared through file_paths.xml), ShareFile, and a file-based PdfReaderDialog.
  • Labels settings sheet and i18n: added about 190 keys in each of the four languages. All keys used in code are present, and none contain an em dash.

⚠️ The version bump no longer matches the PR or STATUS.md

app/build.gradle.kts now ships versionCode = 21 / versionName = "1.8.0". The PR title, the PR body and STATUS.md:3 still say 1.7.0 (versionCode 20). If 1.7.0 never went out, the store build will skip a version. If the release really is 1.8.0, the release notes and STATUS.md are out of date. One of the two needs to change before tagging.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread app/src/main/java/com/pinakes/app/data/repository/StaffCirculationRepository.kt Outdated
One key per submission, kept across the automatic retry after a dropped connection and across a manual retry of the same form, so a request that reached the server is never created twice: a replay answers the original resource. A changed form is a new submission with a new key; a success or a 422 idempotency_conflict discards it, and the conflict has its own message.
Return with outcome and penalty, the penalty validation, the borrower page, a 409 mapped to its message, a new loan with user search and its Idempotency-Key, an ineligible borrower, return by a typed copy code, an unknown code, reservation cancel and create, and the circulation section hiding offline and coming back. The five return outcomes now wrap instead of scrolling sideways.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found in the two new commits. The four inline findings and the version-mismatch note from the last review are still open, because these commits don't touch that code.

Reviewed changes

This review covers the two commits added since the last pullfrog review at f1ad1a3.

  • Idempotency keys on staff creates: added SubmissionKeys, which keeps one UUID per payload and compares payloads by data-class equality. StaffLoanCreateViewModel.withIdempotency sends that key as Idempotency-Key on POST staff/loans and POST staff/reservations. On ErrorCodes.NETWORK it retries once with the same key, and it drops the key after a success or a 422 idempotency_conflict. The conflict has its own message, staff_err_idempotency_conflict, in all four languages.
  • Return sheet: changed the outcome chips to a wrapping FlowRow with the same colours as FilterChipRow, so all five outcomes fit at 360dp. The sheet now has its own title, staff_return_title, and test tags.
  • Circulation device tests: added flows for return with a penalty, penalty validation, a server conflict, loan creation with an idempotency key, cancelling and creating reservations, return by copy code, and the circulation section hiding offline. They come with fixtures and new AppFlowTest helpers.

Pullfrog  | Fix it ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

The book editor lists every copy with its state and notes: a copy can be edited, deleted (the server's refusal for copies on loan, held or with history is shown as such), or added with its own inventory code, and the book and copy label PDFs open in the app or go to the share sheet. Series are browsed as a tree with search and edited in place: details, rename (an existing name asks before merging), merge into another series, delete, parent work, book numbers, removal and multi-select assignment. Duplicate authors are merged from the server's suggestions or a manual pick, publishers by pick, and genres by choosing the one that goes and the one that stays.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues in the new commit. Three minor notes are inline. The four inline findings and the version-mismatch note from the review at f1ad1a3 are still open, because this commit doesn't touch that code.

Reviewed changes

This review covers the one commit added since the last pullfrog review at bf13e1c.

  • Per-copy management in the book editor: added StaffCopiesSection with its own StaffCopiesViewModel. Staff can edit a copy's state and notes (the state is sent only when it changed, and circulation states are shown but can't be picked), delete a copy (the server's copy_has_history refusal is surfaced), add a copy with an explicit inventory code, and download book, copies or single-copy label PDFs. After each change the record is read again, and applyCopies updates only the copies and counters of original.
  • Merge duplicates screen: added StaffMergeScreen, which merges authors and publishers (suggested author groups default to the record with most books, or a manual pick with a chosen primary and optional new name) and moves a genre into another, rejecting a merge into a descendant.
  • Series screens: added a searchable series tree built by the pure SeriesTree helper, with a create sheet that offers the existing series on a 409. Added a detail page with edit, rename (asks before merging on a name clash), merge into a non-descendant, delete, parent-work creation, adding books and per-book numbering. Added the STAFF_SERIES, STAFF_SERIES_DETAIL and STAFF_MERGES routes behind the existing hub entries.
  • Tests and i18n: added CopiesFlowTest, MergeFlowTest, SeriesFlowTest, SeriesTreeTest and their fixtures, plus about 75 keys in each of the four languages.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread app/src/main/java/com/pinakes/app/ui/screens/staff/content/StaffSeriesScreens.kt Outdated
A CSV or LibraryThing export is picked with the system document picker, checked for size and extension, and uploaded with the scraping choice and the field families to update. The job then runs chunk by chunk from the server's own progress: an out-of-order answer continues from where the server is, a busy job is retried after a pause, and the job id lives in the route so it resumes after the process is killed; pause, resume and cancel are there, with the counters and the error lines. Past imports offer their error CSV. Missing covers are fetched one book at a time with progress, stop and the rate limit honoured; the server-side sync and an enrichment batch use the long-timeout client behind a confirmation, and automatic enrichment has its switch. A new loan or reservation is also retried once with the same key on a 409 concurrent_retry. Switch rows now toggle on the whole row.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues in the new commit. Two minor notes are inline. The findings still open from the reviews at f1ad1a3 and ae03e82 are untouched by this commit.

Reviewed changes

This review covers the one commit added since the last pullfrog review at ae03e82.

  • Imports: added StaffImportsScreen. It reads a file picked through the system picker into memory (20 MB cap, extension check), uploads it as a multipart CSV or LibraryThing job with optional scraping and update-field families, lists open jobs and past imports, and downloads the example and error CSVs.
  • Resumable chunk driver: added StaffImportJobScreen and the pure ImportProgress rules. Each chunk starts from the server's processed, a 409 out_of_order continues from meta.processed, and a 409 job_busy waits and retries up to 5 times. Scraping chunks go through the long-timeout client, and an unfinished job resumes automatically when it is opened.
  • Covers and enrichment: added StaffCoversScreen. It fetches missing covers one book at a time with progress and a stop button, and waits for the server's retry_after when rate limited. The server-side cover sync and the enrichment batch sit behind a confirmation, and the automatic-enrichment switch is there too.
  • Shared and circulation tweaks: made the whole SwitchRow toggleable, so there is one touch target and one accessibility node with the switch role. withIdempotency now also retries once with the same key on 409 concurrent_retry.
  • Tests and i18n: added ImportFlowTest, CoversFlowTest, ImportProgressTest, the FakePinakes.sequence and scrollListTo helpers, and about 77 keys in each language. All four files have the same keys and none contain an em dash.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread i18n/en.json Outdated
Donation offers are reviewed by status: accept, reject, receive (into the wanted title, or into a catalogue record picked when the offer names none) and delete, with the closed-offer refusal explained; wanted titles can be marked as received when the copy arrived outside an offer. Archival units, published or not, are listed with search and a publish filter and edited across identity, dates, description, material and links, with the plugin's required fields and year rules checked before sending and the server's field or duplicate reference shown on the field. A saved unit takes authorities with a role, a cover and documents. Authority records have their own list and editor.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The new authority editor can wipe an existing record after its load fails. Fix this before release. The new donation-offer, wanted-title and authority lists also stop at 30 rows. Notes inline.

Reviewed changes

This review covers the one commit added since the last pullfrog review at 3a44e5e.

  • Donation offers: added StaffDesiderataScreen, with status tabs and a wanted-titles tab. Accept, reject and receive are gated by status (OfferActions). When an offer has no linked wanted title, receiving it opens a book picker and sends received_book_id. Marking a wanted title received surfaces the server's open_offers refusal.
  • Archives: added a paged archival-unit list with search and a published filter. The editor is built on the pure ArchiveForm, which sends every plugin key so a cleared field empties its column. It checks required fields and the year range on the client, and maps a duplicate reference code to its field. After the first save the unit can take a cover, documents and authorities with a role.
  • Authorities: added an ISAAR(CPF) authority list and editor (AuthorityForm), plus the navigation entries for the existing hub items.
  • Tests and i18n: added ArchivesStaffFlowTest, DesiderataStaffFlowTest, ArchiveFormStateTest and their fixtures. About 115 keys were added in each of the four languages. All four files have the same keys and none contain an em dash.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

STATUS and the Uwe parity notes describe the circulation desk, the book tools and the plugin content now handled natively. The live smoke creates a loan through the API with an Idempotency-Key (and checks its replay), returns it in the app by typing the copy code, adds a copy with an explicit code, creates a series, follows a two-row CSV import, receives a Desiderata offer and creates an unpublished archive unit; each step is skipped while its health flag is off, and cleanup returns open loans and removes only what the run created. Hub navigation now scrolls to its rows, since the hub grew, and the location step waits for the editor to close instead of the save label, which a spinner replaces while saving.
A merge retried after its records were already merged (409 merge_error with meta.missing) is reported as done and the duplicate list refreshed. A cover the source found but could not download has its own message. The imports screen offers the core LibraryThing TSV export when that import kind is available.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No app code changed in this commit. One minor note on the live smoke cleanup is inline.

Reviewed changes

This review covers the one commit added since the last pullfrog review at 16a3ca9. It changes only docs and the live smoke script.

  • 1.8.0 notes: added a 1.8.0 / versionCode 21 section to STATUS.md and a matching entry to docs/UWE-PARITY.md. STATUS.md now matches app/build.gradle.kts, but the PR title and body still say 1.7.0 / 20. The quoted counts match the source: 369 @Test in app/src/test and 77 in app/src/androidTest.
  • Live smoke phase 2: Api.call now takes extra headers and multipart bodies. Added six steps, each skipped while its /health flag is off: a loan created through the API with an Idempotency-Key (the replay must return the same id) and then returned in the app by typing the copy code; a copy with an explicit code; a series; a two-row CSV import followed to completion; a Desiderata offer received; and an unpublished archive unit that must not appear in the public list. Every S[...] key the steps use exists in i18n/en.json.
  • Smoke cleanup and robustness: cleanup now finds phase 2 books by their run-unique titles, returns any loan the run left open, and then deletes the series, units and offers the run recorded. Hub taps scroll to their entry first, onboarding discovery waits up to 90 s, and the location edit now waits for the editor itself to close instead of the save button's label.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread tools/e2e/live_smoke.py Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues in this commit. The 13 open findings from earlier reviews are still open, because this commit doesn't touch that code. That includes the merge screen's unrendered state.error, the singular/plural strings, and the import job that stays "Paused" after a 410 gone.

Reviewed changes

This review covers the one commit added since the last pullfrog review at 98704f1.

  • Retried merges: a 409 merge_error on an author or publisher merge whose meta.missing is not empty now counts as done. The selection and confirm dialog are cleared, and staff_merge_already_done is shown instead of a save error. The Authors tab reloads its duplicate groups after any merge_error. Meta.missing is new, and MergeFlowTest covers the retry case.
  • LibraryThing export: added an "Export for LibraryThing" button to the imports screen. It is shown only when areas.libraryThing is on, and it downloads staff/exports/librarything.tsv. download now carries the MIME type into exportMime, so ExportFileDialog opens and shares the TSV as text/tab-separated-values instead of text/csv.
  • Error mapping and i18n: mapped cover_download_failed to its own message and added the three new keys to all four languages. None of them contain an em dash.

Pullfrog  | Fix it ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

When the server offers the Digital Library area, the book editor has a Digital content section: an eBook (PDF or EPUB) or an audiobook picked with the system document picker is streamed to POST /staff/books/{id}/digital on the long-timeout client, never held in memory. Without the flag the section is absent.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues in the new commit. Two minor notes on the upload path are inline. The findings still open from earlier reviews are untouched by this commit.

Reviewed changes

This review covers the one commit added since the last pullfrog review at 7e8080f.

  • Digital content in the book editor: added StaffDigitalSection. It appears in edit mode only when staff_digital_library is on and offers two system-picker buttons, one for an eBook (PDF/EPUB) and one for an audiobook. A failure surfaces the server message, and forbidden drops the staff role.
  • Streaming upload: added StaffContentRepository.uploadDigital. It posts a multipart file plus digital_type to staff/books/{id}/digital on the long-timeout client. The body is a custom RequestBody that reopens the document stream on every writeTo (so OkHttp retries work), and its length comes from the provider's SIZE (chunked when that is unknown). HTTP logging is BASIC, so the file is never buffered in memory.
  • Tests and i18n: moved publishDownload (now with a MIME parameter) and pickInDocumentsUi from ImportFlowTest into AppFlowTest. Added DigitalUploadFlowTest, which checks the multipart filename, digital_type and the content, and that the section is hidden without the flag. Added 8 keys in each of the four languages. None of them contain an em dash.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread app/src/main/java/com/pinakes/app/data/repository/StaffContentRepository.kt Outdated
Comment thread app/src/main/java/com/pinakes/app/ui/screens/staff/content/StaffDigitalSection.kt Outdated
The Desiderata step now sends a free offer with a UUID submission id and receives it in the app into the record the run created, instead of picking a wanted title that other test runs may remove; slow copy saves get two minutes. STATUS records the Tier 2 scope (digital upload only) and the 16/16 live run.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. This commit fixes the live smoke cleanup note from the last review. The other findings from earlier reviews are still open, because this commit doesn't touch that code.

Reviewed changes

This review covers the one commit added since the last pullfrog review at 9e7e6c0. It changes only STATUS.md and the live smoke script.

  • Self-contained Desiderata step: desiderata_receive now posts a free offer with no book_id and a random submission_id. It receives the offer through the "Which record gets the copy?" picker into the run's own CIRC_TITLE book and checks that received_book.id matches. The new copy lands on a book the run created, and cleanup deletes the offer before deleting that book, so nothing remains on pre-existing records. The step is skipped when that book doesn't exist.
  • Smoke timing: raised the copy-with-code wait to 120 s, and added a short pause so the offer sheet can finish expanding.
  • STATUS.md: updated the device test count to 80, which matches the @Test count in app/src/androidTest (app/src/test is still 369). Also recorded a green live run (16 passed, 0 skipped, 0 failed) and moved digital upload into the shipped Tier 2 notes.

Pullfrog  | Fix it ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Counts in the staff texts used a counted noun after the number ("1 books", "1 Bücher", "1 rows"); the i18n pipeline generates plain strings, so they now read "Books: 1" style in all four languages: duplicate groups, series counts, delete and merge messages, missing covers, enrichment, import progress, bulk extend, days overdue, genre merge, authority units, merged records and cover sync. A unit test fails if a staff string puts a counted noun right after a number again. New texts for select all, an import that ended elsewhere and a document over 200 MB.
- A load-more interrupted by a reload no longer leaves the loan list stuck as loading.
- Reservations, donation offers, wanted titles and authority records page past the first server page.
- Bulk extend and bulk recall send the whole selection in batches of the server limits (500 and 50) and sum the counts; a Select all action covers every loaded loan.
- Archive documents and digital files are streamed from the provider, never read into memory; a provider that refuses the stream becomes an IOException, an ordinary failure instead of a crash on OkHttp's thread; provider lookups for digital files run off the main thread.
- Staff exports (receipts, labels, error CSVs) are deleted on sign out and on instance switch.
- An authority that failed to load shows an error with Retry and can no longer be saved over.
- Clearing a series' order sends an explicit null, so the server clears it.
- An import answered with 410 gone shows that it ended elsewhere and offers no Resume or Cancel.
- A failed duplicate list is reported with Retry instead of "no duplicates", and an old error no longer shows in the merge sheet.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. The two new commits fix all 14 open findings from earlier reviews, and those threads are now resolved. One thing is left, and it is in the PR metadata, not the code: the title and body still say 1.7.0 / versionCode 20, while app/build.gradle.kts and STATUS.md say 1.8.0 / 21.

Reviewed changes

This review covers the two commits added since the last pullfrog review at 126b167.

  • Count strings: rewrote every staff string that put a counted noun right after a number (for example "1 books") into the "Books: %d" form, in all four languages. StaffStringsPluralTest now fails the build if a new one appears.
  • Bulk loan actions: changed bulkRecall and bulkExtend to send every selected id in batches of 50 and 500, summing the counts, instead of silently dropping the extra ids. The loans list now has a "Select all" button.
  • Paging: added load-more for reservations, donation offers, wanted titles and authorities. The loans list no longer gets stuck "loading" when a reload overlaps a page fetch.
  • Failed loads: the authority editor now shows an error with Retry and can't be saved until the record has loaded. The merge screen reports a failed duplicate list with a retry, instead of saying there are no duplicates.
  • Ended imports: a 410 gone now marks the job as ended elsewhere, re-reads it from the server, and hides Resume and Cancel.
  • Series edits: SeriesForm.toJson sends every key, so clearing the order sends an explicit JSON null and the server empties the field.
  • Uploads and exports: added a shared StreamingBody. It turns provider errors into an IOException, so a failed read shows an error instead of crashing the app. Archive documents now stream on the long-timeout client instead of being read into memory. File lookups (query/getType) now run on Dispatchers.IO. clearHttpCache now also deletes cache/exports/ on logout and instance switch.
  • Tests: added ReviewFixesFlowTest, with one device test for each of these fixes, plus its fixtures.

Pullfrog  | View workflow run | Using claude-opus-5-5 | 𝕏

The Tier 2 bridges get native screens, each shown only with its health flag.
- Periodicals desk: an arriving issue is scanned (EAN-13) or typed and looked up, then received or claimed; mastheads open on their years, whose issues show their state with receive or claim, and take new years, new issues and subscriptions (supplier, cost, dates, renewal).
- Book clubs: clubs are listed and created; a club's settings, its reading workflow (states with label, colour and voting, reading-now and archived marks, reordered, added and removed) and its members (role, membership state, adding a borrower) are edited, and a club can be deleted.
- FRBR works: works are searched, created, edited and deleted, take expressions (translations, revisions, critical editions...) and have books linked or unlinked.
The scanner viewfinder with its camera permission handling is now shared by the copy scanner and the periodicals desk.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The new Tier 2 screens have two behaviour bugs. Fix them before the server turns these flags on. Scanning an issue with the camera can't identify the issue, because the barcode add-on is dropped. Saving a book club workflow also strips any state flag the app doesn't model. A stale STATUS.md note is inline too.

Reviewed changes

This review covers the one commit added since the last pullfrog review at 80d7173.

  • Periodicals desk: added a scanner and manual code entry that look up an issue (scan-lookup), then receive or claim it depending on its status. Below the scanner is a searchable, paged list of mastheads. Each masthead page has expandable years with their issues, plus forms to add an issue or a year and to create or edit a subscription.
  • Book clubs: added a club list with a create sheet. Each club page has settings, a workflow editor (at least two states, which can be reordered, added or removed, each with voting/current/archived flags), member role and status chips, adding a member by user search, and delete.
  • FRBR works: added a work search (up to 100 results) and a create sheet. Each work page has an edit form, add/edit/delete for expressions, and linking or unlinking books.
  • Shared scanner and plumbing: moved the copy scanner's camera viewfinder into ScannerBox. Added StaffTier2Api/StaffTier2Repository (every call goes through staffCall), the pure Tier2Forms, and new routes and hub rows, each gated on its own area. StaffAreas.anyPlugin now covers the three new areas.
  • Tests and i18n: added Tier2FormsTest (7 tests), Tier2FlowTest (6 tests) and their fixtures, plus about 135 keys in each of the four languages. All four files have the same keys and matching placeholders, and none of them contain an em dash.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread app/src/main/java/com/pinakes/app/data/staff/Tier2Forms.kt
Comment thread STATUS.md Outdated
The live smoke looks up an unknown code at the periodicals desk (read-only: the bridge offers no deletes), and creates a book club and an FRBR work in the app, removing them in cleanup; long forms scroll to their Save button. STATUS and the Uwe parity notes now describe the periodicals desk, book club management and FRBR works as native, with the 19/19 live run.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues in this commit, and it fixes the stale STATUS.md note from the last review. Two findings from the review at b8db4c6 are still open because this commit changes no app code: the camera path drops the periodical's EAN add-on, and workflow saves strip state flags the app doesn't model. STATUS.md now says an arriving issue can be scanned, but with the camera that only works after the add-on fix.

Reviewed changes

This review covers the one commit added since the last pullfrog review at b8db4c6. It changes only docs and the live smoke script.

  • STATUS.md and parity notes: rewrote the Tier 2 entry to describe the periodicals desk, book clubs and FRBR works as shipped, each behind its own flag, and updated the Italian 1.8.0 summary in docs/UWE-PARITY.md to match. The new counts (377 unit tests, 92 device tests) match the @Test totals in app/src/test and app/src/androidTest.
  • Live smoke Tier 2 steps: added periodicals_desk, which first checks through the API that a fixed code has no match and then looks it up in the app without changing anything. Also added club_create and work_create, which create a record in the app and confirm it through the API. Each step is skipped while its flag is off. Every S[...] key they use exists in i18n/en.json, and the API paths match StaffTier2Api.
  • Cleanup: clubs and works are looked up again by their run-unique names, so a step that fails partway still gets swept. Each one is then deleted by id.

Pullfrog  | Fix it ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Server labels and error messages followed the installation's language, so an English app showed "Prestato" next to English text. The interceptor now adds Accept-Language with the language picked in Profile (or the device one) unless the request sets it; Mobile API 1.7.0 answers in it when the installation has that language and keeps its own otherwise.
At 360 dp the Reservations tab wrapped mid-word ("Reservation" / "s"), and German and Italian labels are just as long. The tabs are now a scrollable row with single-line labels, like the circulation desk.
Adds 24 screenshots of the 1.8.0 staff area (circulation desk, cataloguing and plugin content), captured on Android 15 at 360 dp against a local instance with demo data and resized to 720 px, and lists the staff area, circulation desk, offline catalogue and loans calendar in the features table.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues in the two new commits. Two findings from the review at b8db4c6 are still open, because these commits don't touch that code: the camera path drops the periodical's EAN add-on, and saving a workflow strips state flags the app doesn't model.

Reviewed changes

This review covers the two commits added since the last pullfrog review at 9be8401.

  • App language on every request: AuthInterceptor now adds Accept-Language from AppLocale.current() before it handles the no-auth marker. Public calls and the long-timeout client get the header too, since that client is derived from the same okHttpClient. A header set on the request itself wins, and nothing is sent for a blank or und tag. AcceptLanguageTest covers all three cases.
  • My Library tabs: replaced TabRow with ScrollableTabRow(edgePadding = 0.dp) and made the labels single-line, so "Reservations" / "Vormerkungen" no longer wrap mid-word. The tabs now sit at their natural width, starting from the left, instead of filling the row evenly.

One low-confidence note, not raised inline because it depends on the server: the HTTP cache is keyed by URL only and isn't cleared when the language changes. If the server's ETag stays the same across languages, a 304 after a language switch would replay the response in the old language until the data changes.

Pullfrog  | Fix it ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

@fabiodalez-dev fabiodalez-dev changed the title Release 1.7.0: staff cataloguing with ISBN scanning, offline catalogue and a loans calendar Release 1.8.0: staff area with cataloguing, circulation and plugin content, offline catalogue and a loans calendar Oct 14, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant