The "Edit address" field in Settings only accepted an IPv4 address
(IPV4.test), rejecting a bare hostname (e.g. "timbratore") or FQDN
(e.g. "timbratore.lan") even though the underlying connection (Rust's
ToSocketAddrs) already resolves those fine. Onboarding had no such
restriction. Replaced the IPv4-only check with isValidHost (IPv4 OR a
standard DNS hostname), dropped the numeric-only keyboard hint on that
field, and reworded the labels/placeholder error to not imply IP-only.
Also makes "Edit address" test the connection before accepting it,
instead of saving unconditionally: ActionButton gained a `disabled`
prop so the confirm/cancel buttons can be locked (with a spinner on
confirm) while the test is in flight, and the edit form now only
closes on a successful test, staying open with the raw error shown
otherwise.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
The review screen now tracks whether there are edits not yet exported
and asks for confirmation before discarding them: clicking back, or
closing the app window entirely (intercepted via onCloseRequested,
which needed a core:window:allow-destroy capability that wasn't
granted by core:default). The confirmation dialog (ConfirmDialog.svelte)
is generic, built on a shared ModalFrame extracted from ExportModal so
the two don't duplicate the backdrop/box styling.
Also adds undo: a per-row button (icon-only, left of the note, shown
when that day has edits) reverts just that day instantly, and a
per-employee "undo all" (icon in a red box, last among the header's
tools) reverts every day for the current employee after a confirmation
dialog, since that one can't be undone itself.
ActionButton moves out of review/ next to IconButton/PillButton, since
ConfirmDialog (outside the review feature) now uses it too.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Employee names are now cached locally (employees.toml, keyed by reader
index then employee id) instead of being re-fetched from the time
clock on every month: load_employees only asks the device for ids not
already in the cache. When a month has employees the cache doesn't
know yet, a new screen (NewEmployees.svelte) lets the user name them
before continuing to the review screen; the chosen names are local to
Oxibadge only, never written back to the device. The same cache can be
managed afterwards from a drawer reachable from the time clock card in
Settings ("Gestisci dipendenti"), reusing the rename-in-row pattern
from the original mockup.
Settings.readers is a list again (was flattened to a single `reader`
object) so multiple time clocks can be supported later; only the first
one is used today.
Also:
- buildEmployees() now derives the employee list from the month's
records instead of from every cached name ever seen, so the review
screen no longer shows employees unrelated to the selected month.
- Renaming an employee from the review screen's pencil button now
actually persists to the cache (the callback existed but was never
wired up).
- Translated to English a number of Rust/TS/Svelte comments that had
been written in Italian; only the i18n locale files should contain
Italian.
- Minor dedup: extracted PillButton.svelte (was duplicated inline in
ReaderCard), promoted the repeated .mono CSS rule to app.css, moved
initials() out of the review feature into $lib/text.ts.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Add a settings screen (gear button on the month-confirmation screen)
to configure the expected shift times, the rounding step, the
interface language, and the time clock address — replacing the old
onboarding-only connection flow. Settings persist to the same
preferences.toml as the reader address, under a single load_settings/
save_settings command pair.
The review screen's "fix shifts" action now redistributes stacked
punches by nearest configured shift time instead of blind position,
and a stacked cell manually cleared down to one entry no longer
leaves a dangling empty input.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Add the month-review screen shown after records/employees load: an
editable per-employee timesheet table with issue/rounding detection,
bulk fixes (reorder stacked punches, add lunch break, round to step),
and an export flow (single employee or all) wired to a new Rust
export_timesheet command. Split into focused components under
review/ instead of one large file, with shared i18n keys and a new
export_timesheet IPC command.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
load_records/load_employees failures were only logged to the console
and silently reset the UI to the month picker. Errors now flow through
a shared store (src/lib/error.ts) into an ErrorBanner, reused by both
Onboarding and ConfirmMonth, with the raw message passed straight
through from Rust (no stable-code classification layer — these are
mostly plain I/O errors, not worth categorizing).
i18n stays grouped per screen as before, with error-related strings
split into their own top-level "errors" group.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Adds the screen after onboarding: pick a month/year (ConfirmMonth), show
loading screens while the device is queried, then download that month's
records and resolve each distinct employee's name, printing both to
stdout (debug pretty-print) before landing on the placeholder "done"
screen.
- r701: RecordIterator's boxed stream is now Send, required for it to
cross into a Tauri command's future.
- oxibadge: load_records/load_employees commands, sharing the existing
3-retry connect_with_retries helper. load_records uses skip_while/
take_while on the device's newest-first stream to grab only the
target month without walking its whole history, then a single
collect + in-place Vec::reverse() back to chronological order.
- Shared LoadingScreen/Intro components extracted so future screens
reuse the same look without duplicating markup/CSS.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Adds the first real screen: a setup form for the device's host/port,
tested against the real device (3 retries) and persisted to
preferences.toml before showing a placeholder "done" screen.
- Rust: test_connection/save_preferences/load_preferences commands;
preferences stored as [[reader]] in TOML so more readers can be
added later without a format change.
- Svelte: split the monolithic page into reusable components
(Screen, Column, Field, Button, ErrorBanner, Hint) under
src/lib/components, sharing design tokens from app.css.
- Automatic it/en i18n via svelte-i18n, locale detected from
navigator.language.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Scaffolds the oxibadge crate/frontend as a new workspace member, stripped
down to a bare hello-world, and
extends the CI pipeline
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Moving the library into its own r701/ directory makes room for the
Tauri GUI client (Oxibadge) to be added as a sibling workspace member
in a following commit. edition/license/repository are now inherited
from the new workspace root instead of being duplicated per member.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- fake_records now generates a full year (2026) of classic workday
punches (09:00-13:00, 14:00-18:00) for the employees in fake_users;
this spans many 1024-byte blocks, exercising the multi-block
streaming path
- fake_users/fake_records are now public so tests can assert against
them instead of hardcoded expectations
- scoped #[allow(clippy::unwrap_used)] to only the functions that
actually use unwrap, instead of a blanket file-level allow
- added gets_total_record_count, streams_records, and
gets_none_for_unknown_employee tests
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
tests/mock/mod.rs emulates the device's TCP protocol (ping, get_name,
get_total_record_count, get_record_bytes), grounded in the reverse
engineering notes at
https://nicolabelluti.me/posts/2024/05/studying-a-communication-protocol/,
so tests/e2e.rs can exercise the real R701 client against it instead of
needing a physical reader. Starts with one test (connect + ping);
more scenarios (get_name, record streaming, error cases) to follow.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Added rustdoc comments to every public item (R701, Record, Clock,
RecordIterator and their methods/fields/variants), each with an
# Errors section where applicable, so the clippy::missing_errors_doc
allows are no longer needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- request() now reads exactly the expected response length with
read_exact instead of a single non-guaranteed fill_buf, fixing a
latent bug where a response split across multiple TCP packets could
be mistaken for malformed data; the per-method length checks are now
redundant and were removed
- sequence_number wraps explicitly via wrapping_add instead of
relying on implicit (and debug-vs-release inconsistent) overflow
behavior after 65536 requests
- get_name validates UTF-8 strictly instead of silently lossy-decoding
invalid bytes; on invalid data it now returns the FromUtf8Error
itself (byte-level \0 trimming moved after the UTF-8 check), letting
the caller choose to fail or fall back to a lossy rendering
- removed the unused Default derive from Clock and Record
- removed npm from the CI image (no JS/Node in this repo), fixed a
typo in a CI step name
- fixed the Cargo.toml license field to the actual SPDX identifier
(AGPL-3.0-or-later, per the notice at the end of LICENSE) instead of
the free-text "GNU AGPLv3.0"
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>