Commit Graph
330 Commits
Author SHA1 Message Date
rzuastiandClaude Opus 4.8 dedfe1c838 Fix ghosting in native screen transitions
Routed screens are transparent fragments rendered into the shell's single
Scaffold. A screen pushed on top (e.g. device detail) was therefore
see-through, leaving the screen beneath visible as it slid in, and the fade
page ignored secondaryAnimation so the covered screen sat frozen instead of
parallaxing out.

- Paint each routed page opaque (theme surface) so pushes cover cleanly.
- Drill into detail screens with a CupertinoPageTransition slide; the covered
  page parallaxes out via the same route animation, so both move in lockstep
  on the native iOS curve. Tabs keep their crossfade.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:29:57 -04:00
rzuastiandClaude Opus 4.8 80f0f54785 Serve front-end assets with Cache-Control: no-cache
Without an explicit directive ServeDir let browsers heuristically cache
the Flutter bundle, and the service worker kept serving stale assets, so
new icons/images did not appear after an upgrade (even on hard reload).
Force revalidation so users always get the latest assets; unchanged files
still return a cheap 304.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:15:52 -04:00
rzuastiandClaude Opus 4.8 a12ff62fbd Document webhook prerequisite in codemagic.yaml
Cutting v0.1.3 pushed the tag but no build started: the repo had no
webhook delivering events to Codemagic, so the tag trigger never fired.
Note that the triggering block alone is not enough and explain the two
ways to wire delivery (GitHub App or a manual repo webhook).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 16:36:35 -04:00
rzuasti 4cf8759888 Release v0.1.3 v0.1.3 2026-06-07 16:14:03 -04:00
rzuasti 645cd2c543 Updated release notes 2026-06-07 16:13:02 -04:00
rzuastiandClaude Opus 4.8 f3d845ce37 Add device identification guide dialog for unregistered devices
Adds a "How to identify this device" button next to Register Device that
opens a dialog with personalized clues (vendor/type) and steps to track
down the physical device on the network.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 16:10:50 -04:00
rzuastiandClaude Opus 4.8 3d7c4a1323 Shrink normal device-seen dots in event history chart
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:49:44 -04:00
rzuastiandClaude Opus 4.8 4a19ab34e2 Crossfade between top-level tabs instead of iOS slide
Switching between the top-level destinations went through the default
CupertinoPage slide, which is meant for forward pushes. For a peer
tab-switch it slid the incoming screen in over the outgoing one without
animating the old screen away, leaving it visible in the background.

Give the top-level routes a CustomTransitionPage crossfade; keep the
device-detail route on the default slide since it is a genuine drill-in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:44:48 -04:00
rzuastiandClaude Opus 4.8 55eb878590 Reword empty new-notifications message
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:37:30 -04:00
rzuastiandClaude Opus 4.8 d4e00515fc Remove Notifications title from home page body
Match the structure of other pages by dropping the standalone title and
placing the filter selector and "mark all as read" action in one row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:36:05 -04:00
rzuasti 9099d0f6a7 Updated todo 2026-06-07 15:33:21 -04:00
rzuastiandClaude Opus 4.8 40a7f3a432 Request iOS local-network permission at app launch
The iOS local-network permission prompt fires on the first local-network
access. That used to be the user's "Test" tap in settings, so the prompt
appeared mid-request and the first connection attempt always failed.

Provoke the prompt at launch instead via an mDNS multicast datagram, so the
permission is settled before the user reaches settings. Platform exclusion is
handled with a conditional import: web gets a no-op stub (keeping dart:io out
of web builds) and the native path no-ops off iOS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:33:06 -04:00
rzuastiandClaude Opus 4.8 87af782985 Use the OOTT brand icon as the app launcher icon on all platforms
The app shipped Flutter's default icon on every platform. Add
flutter_launcher_icons (mirroring the existing flutter_native_splash
setup) with an OOTT wordmark on the brand orange (#fe8019) and
regenerate the native icon sets for iOS, Android, macOS, Windows and
web.

Android uses an adaptive icon (orange background + inset transparent
foreground) so the mark survives any launcher mask; the square
full-bleed source is used elsewhere where the platform applies its own
corner mask. Source images live under assets/icon/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:24:38 -04:00
rzuasti 032923cdae Updated todo 2026-06-07 12:40:48 -04:00
rzuasti af3faa2786 Updated todo 2026-06-07 12:40:31 -04:00
rzuastiandClaude Opus 4.8 a81e510cde Note the TestFlight build trigger in release.sh output
Pushing a vX.Y.Z tag kicks off the Codemagic iOS build automatically;
surface that in the release output so the operator knows it's running.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 12:40:18 -04:00
rzuastiandClaude Opus 4.8 5a2872e602 Declare app as exempt from export encryption rules
The app's only encryption is HTTPS/TLS (accessed via iOS) plus trivial
local XOR obfuscation, so set ITSAppUsesNonExemptEncryption to false.
This is the correct export-compliance classification and stops App Store
Connect from prompting on every upload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 12:36:05 -04:00
rzuastiandClaude Opus 4.8 a685d9abfa Use a persistent signing key for iOS code signing
Codemagic build VMs are ephemeral, so fetch-signing-files without a
persistent private key creates a distribution certificate that can't be
reused on later builds ("Cannot save Signing Certificates without
certificate private key"). Pass a CERTIFICATE_PRIVATE_KEY secret so the
certificate is created/reused with a key Codemagic owns.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 11:58:50 -04:00
rzuastiandClaude Opus 4.8 59872bde1f Point CI at the "Codemagic" App Store Connect integration
Match the App Store Connect API key name registered in the Codemagic UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 11:48:38 -04:00
rzuastiandClaude Opus 4.8 8dee23d18c Make TestFlight build number safe for the first build
get-latest-testflight-build-number errors when the app has no builds
yet, which broke the first CI run. Fall back to 0 so the initial build
number is 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 11:43:22 -04:00
rzuastiandClaude Opus 4.8 d485f4b335 Add iOS TestFlight CI via Codemagic
Set up automated cloud iOS builds and TestFlight distribution without a
local Mac:
- Add codemagic.yaml: iOS->TestFlight workflow with automatic signing,
  build-number auto-increment, triggered by the vX.Y.Z release tags.
- Set the iOS bundle identifier to net.oott-security.app (was the
  placeholder com.example.frontend).
- Set the iOS display name to OOTT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 11:31:53 -04:00
rzuasti f547cf1279 Release v0.1.2 v0.1.2 2026-06-07 09:17:09 -04:00
rzuasti b1302a1eec Added release notes for 0.1.2 2026-06-07 09:16:11 -04:00
rzuastiandClaude Opus 4.8 09e7915547 Separate events and notifications into focused modules
The events.rs file mixed three domains: device-event recording, change
detection, and the entire notification pipeline (rendering + delivery +
sending). This made it long, gave functions side effects beyond their
stated goal (classify_* silently recorded events), and intertwined the
events and notifications logic.

Split along domain boundaries:
- model::device_events now owns DeviceChange, the shared contract.
- events records device events only (record_new_device/record_known_device);
  events/detection.rs holds pure change detection.
- new notifications module owns rendering, delivery, and sending
  (notifications.rs + delivery.rs + render.rs); pushover/error moved here.

Data now flows one way: events produces DeviceChange, notifications
consumes it, both depend only on model. Scanners/pipeline/main orchestrate.
classify_* renamed to record_* so the write is the stated goal; send and
send_notification collapsed into persist_and_deliver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 09:10:49 -04:00
rzuastiandClaude Opus 4.8 9efe84c099 Add DeviceChanged and DeviceBackOnline event types
Record what happened on each known-device sighting instead of only
"seen": a baseline DeviceSeen (history heartbeat, no notification) plus
DeviceChanged and DeviceBackOnline events, each deduplicated
independently so a recent routine sighting no longer suppresses a
genuine change or return notification. The frontend chart now trusts the
event type for its marker and tooltip rather than comparing each event's
snapshot against the device's current state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 08:47:56 -04:00
rzuastiandClaude Opus 4.8 27cb2e1d62 Apply event deduplication window to notifications and refine its key
Gate notification persistence and delivery on the device-event
deduplication window, not just the device_events table: record_event now
reports whether it recorded, and classify_new_device/classify_existing_device
suppress their changes when the sighting is a duplicate.

Treat an empty->value IP fill as not a change via a new ip_changed helper
(mirroring vendor_changed), so a device gaining its first address raises no
"changed" notification.

Key deduplication on (mac, scanner, event_type) instead of (mac, ip,
scanner), so repeated sightings of the same kind collapse regardless of the
reported address.

Also delete stale oott.db-wal/-shm in run_tests.sh to avoid init_db panics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 08:17:24 -04:00
rzuasti 2b808a0147 Updated todo 2026-06-06 16:51:01 -04:00
rzuastiandClaude Opus 4.8 b0222445fb Fix API docs link when app is served from the backend
The API docs nav entry uses the origin-relative path "/api/docs", which
Uri.parse left scheme- and host-less, so canLaunchUrl/launchUrl could not
open it (e.g. in Docker). Resolve external links against the current page
so they carry a scheme and host; absolute URLs pass through unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:50:43 -04:00
rzuasti 37075294e3 Release v0.1.1 v0.1.1 2026-06-06 16:31:59 -04:00
rzuastiandClaude Opus 4.8 a69d1aa91a Add v0.1.1 release notes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:29:48 -04:00
rzuasti 3f575f816d Updated todo 2026-06-06 16:25:21 -04:00
rzuastiandClaude Opus 4.8 45891876e9 Require gh login and run front-end tests before release
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:24:59 -04:00
rzuastiandClaude Opus 4.8 3ebb35933e Drop the status block from new-device notifications
New devices are never registered, so the status line only ever read
"Not registered" and added no information. Remove it from the single
new-device notification body (the consolidated summary never had one).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:22:38 -04:00
rzuastiandClaude Opus 4.8 f0d939f685 Add API Docs link to the wide navigation rail
Add an "API Docs" navigation entry that opens /api/docs in a new tab.
It is shown only in the wide-mode navigation rail (not the compact
bottom bar) and sits just before About.

Generalises the navigation destination model so an entry can be an
in-app route or an external link, and can be restricted to wide layouts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:20:29 -04:00
rzuastiandClaude Opus 4.8 cf7dd96415 Redirect bare "/" to the /web UI
The root URI had no content of its own and only showed a pointer
message; send visitors straight to the front-end instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:11:38 -04:00
rzuastiandClaude Opus 4.8 a51b9c06dc Display unknown values as a dash and centralise repeated literals
Backend: notifications now show a plain "-" for an absent name, vendor, or
device type (was empty string / "(unknown)" / "Unknown"), via a single
UNKNOWN_PLACEHOLDER constant.

Frontend:
- Empty/unknown values render as an em dash everywhere, centralised in a new
  Placeholders.emptyValue constant (replaces inline '—' and '(unknown)').
- Route paths moved to a new Routes class, used by the router and every
  navigation call site.
- Device event type modelled as a DeviceEventType enum mirroring the backend
  (NewDevice/DeviceSeen) instead of bare string comparisons.
- Hardcoded EdgeInsets/SizedBox spacing replaced with existing Insets tokens.

Tests and formatting updated; all backend and frontend tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:07:05 -04:00
rzuastiandClaude Opus 4.8 cf8979f63d Fix blank /web UI and /api route under the bundled server
The Flutter web bundle was built with the default base href of "/" but is
served under "/web/", so index.html loaded while every asset reference
resolved to the site root and 404'd, leaving a blank page. Build the bundle
with --base-href=/web/ so asset URLs match the mount point.

Also correct the root guidance text (the API explorer is at /api/docs, not
/api) and add an /api -> /api/docs redirect for convenience.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 12:25:07 -04:00
rzuasti d5d5ffa455 Release v0.1.0 v0.1.0 2026-06-06 11:54:25 -04:00
rzuasti 53725d5292 Adjusted current version to enable release of 0.1.0 2026-06-06 11:53:32 -04:00
rzuasti 64f8737da0 Updated release notes 2026-06-06 11:50:52 -04:00
rzuastiandClaude Opus 4.8 5d080bb4c6 Simplify README storage section and add auth/access-control docs
Trim the storage considerations down to assumptions, per-scenario
estimates, and the levers to control DB size. Add an authentication
and access-control section noting OOTT has no built-in user management
and warning not to gate /api behind an external auth layer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 11:42:55 -04:00
rzuastiandClaude Opus 4.8 42310a36e9 Consolidate active-scan notifications and scrub private data
Active scanners (ARP, SNMP) now accumulate every change across a whole
scan and emit one notification per type via events::notify: a single
device produces the usual single-device notification (carrying its MAC),
while two or more produce one consolidated summary with an empty
mac_address. Device events are still recorded per device.

Notification bodies no longer include MAC or IP addresses; the title
MAC fallback is masked to the last two octets. Summaries list up to
three devices then "…and N more devices".

Split sighting handling so record_sighting persists + records the event
and returns Vec<DeviceChange>; passive listeners (mDNS, SSDP, DHCP) use
record_and_notify since they see one device per event.

Also fixes NotificationType::from_str never mapping "DeviceChanged",
which made those notifications round-trip from the DB as Other.

Frontend: the card already hides the device link when mac_address is
null; added widget tests for the present/absent link cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 11:31:13 -04:00
rzuastiandClaude Opus 4.8 6eaaff457d Add collapsible navigation rail in wide layout
Let users collapse the wide-mode NavigationRail to an icons-only compact
view to reclaim horizontal space. A bottom-pinned double-chevron toggle
sits centred when compact and slides to the rail's right side when
extended, animating in sync with the rail. The choice is persisted via
the nav_rail_extended preference (defaults to extended).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 10:56:25 -04:00
rzuastiandClaude Opus 4.8 5c5087d687 Fix startup WAL race and stale sidecar I/O errors
Enabling WAL per pooled connection raced several concurrent journal-mode
switches when r2d2 eagerly opens the pool at startup, producing transient
"disk I/O error" and "database is locked" failures. WAL is a persistent
property of the database file, so enable it once in init_db (before the
scanners and web server start) and keep only the genuinely per-connection
pragmas (busy_timeout, synchronous, foreign_keys) in the pool initializer.

Also remove the -wal/-shm sidecars in run.sh: deleting oott.db but leaving
a stale write-ahead log behind makes SQLite open the fresh database against
an orphaned log and fail with "disk I/O error".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 09:46:44 -04:00
rzuasti 393e545501 Updated todo 2026-06-06 09:39:15 -04:00
rzuastiandClaude Opus 4.8 15bbb3bda9 Improve backend DB concurrency and async safety
Tune SQLite and remove blocking calls from async/scan paths:

- Enable WAL + synchronous=NORMAL + busy_timeout + foreign_keys on each
  pooled connection, so the five scanners, web server, and retention no
  longer contend on the default rollback journal / FULL fsync.
- Run DB work in axum handlers via spawn_blocking (db::run_blocking) so
  synchronous rusqlite calls no longer block tokio worker threads.
- Deliver notifications on a dedicated task fed by a bounded channel; the
  blocking Pushover HTTP call runs in spawn_blocking, so a slow or
  unreachable Pushover can never stall device discovery.
- Make get_db_connection() return Result instead of panicking, so pool
  exhaustion surfaces as a 500 rather than crashing the process.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 09:30:15 -04:00
rzuastiandClaude Opus 4.8 8749ff33c1 Consolidate backend scanner status, paging, and query-param duplication
Add ActiveStatusCell/PassiveStatusCell wrappers in the scanners common
module so the five per-scanner status.rs files reduce to a single static;
replace parse_parameter_bool/int/string with one generic parse_parameter
over FromStr; extract the shared LIMIT/OFFSET paging clause into
db::apply_paging; and drop a no-op for-loop in the ARP sender.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 09:03:00 -04:00
rzuasti c022e672d1 Updated todo 2026-06-05 21:58:52 -04:00
rzuasti 48d73fb207 Consolidate frontend scanner, pagination, and tick-timer duplication
Collapse the five scanner status models into two shared shapes,
ActiveScannerStatus and PassiveScannerStatus, mirroring the backend's
active/passive vocabulary. Replace the five near-identical per-scanner
detail card files with a single scanner_status_cards.dart (two shared
resolvers plus a config list), and rebuild the combined home card to
iterate a list of scanners with two shape resolvers instead of five
copy-pasted resolve methods.

Extract two reusable mixins:
- PeriodicRebuild: the shared once-a-second "rebuild to refresh elapsed
  text" timer used by the scanner cards and the stale indicator.
- PaginatedListState: the shared pagination state, page-size/page-count
  getters, cancel-token-aware fetch orchestration, and disposal used by
  the device and notification lists.

No behaviour change; ~900 lines removed. Tests and analyzer pass.
2026-06-05 21:57:58 -04:00
rzuastiandClaude Opus 4.8 6b044a6739 Count distinct devices (by MAC) in active scanner status
The ARP and SNMP scanners reported every online sighting, so a device
seen on multiple IPs or via duplicate ARP replies was counted more than
once. Fold the dedup into ActiveStatus::record_scan, which now takes the
device slice and reports the number of distinct MAC addresses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 21:19:06 -04:00