Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
b0a1292
docs(error-handling): consolidate guides + align constitution + fix s…
HankYuLinksys Jun 30, 2026
9c185ff
fix(dashboard): resolve card clipping and add mascot outside dismiss
AustinChangLinksys Jun 29, 2026
4075bfd
feat(components): add optional secondary action to ServiceErrorView
HankYuLinksys Jun 30, 2026
c974dc9
refactor(l10n): migrate AsyncValue error pages to shared ServiceError…
HankYuLinksys Jun 30, 2026
5bbd315
refactor(l10n): migrate diagnostics error pages to ServiceErrorView; …
HankYuLinksys Jun 30, 2026
c5e61b3
docs(error-handling): unify AsyncValue pages on ServiceErrorView in g…
HankYuLinksys Jun 30, 2026
ed4fd9e
fix(l10n): address PR review — error titles, compact card, missed cal…
HankYuLinksys Jul 1, 2026
4860183
docs(error-handling): sync implementation guide with review fixes (ti…
HankYuLinksys Jul 1, 2026
dd9ce40
feat(wifi): add channel dropdown to edit dialog (#1023) (#1027)
AustinChangLinksys Jul 2, 2026
d1df169
fix(dashboard): unify card rows + navigation fixes (#1017)
AustinChangLinksys Jul 3, 2026
d7a9cc3
fix(dashboard): consistent device counts excluding mesh nodes (#1020,…
AustinChangLinksys Jul 2, 2026
6cbfd7f
test(topology): add defensive test for RSSI→LinkQuality SSoT consiste…
AustinChangLinksys Jul 2, 2026
d43a10e
fix(dashboard): child node client signal + trend Y-axis (#1043, #1044)
AustinChangLinksys Jul 2, 2026
99f1bac
fix(analytics): address review feedback for device analytics (#1053)
AustinChangLinksys Jul 3, 2026
080a144
style: dart format usp_device_analytics_notifier.dart
AustinChangLinksys Jul 3, 2026
394bf3b
fix(analytics): set _historyLoaded=true on load failure to unblock pe…
AustinChangLinksys Jul 3, 2026
8fc2a40
feat(auth): replace password storage with session token persistence
AustinChangLinksys Jun 30, 2026
b639538
fix(auth): address review feedback for session token persistence
AustinChangLinksys Jun 30, 2026
76df6bd
style: format dashboard_orchestrator.dart
AustinChangLinksys Jun 30, 2026
c772492
fix(auth): handle relogin failure after password change (#1013)
AustinChangLinksys Jul 3, 2026
93e76ca
chore: bump version to 2.6.0
AustinChangLinksys Jul 6, 2026
50d12cb
fix(wifi): unify guest WiFi detection on canonical alias rule
HankYuLinksys Jul 3, 2026
6108b1f
fix(wifi): warn when no SSID matches the -guest alias rule
HankYuLinksys Jul 3, 2026
9978d4e
fix(wifi): write both SSID.Enable and AccessPoint.Enable for network …
HankYuLinksys Jul 7, 2026
8787ba9
fix(wifi): ensure L1 cache invalidation on partial failure
HankYuLinksys Jul 8, 2026
5a4c6b5
fix(web): move validation to unfocus to prevent TextField focus loss …
AustinChangLinksys Jul 8, 2026
31e4667
fix(dhcp): reject duplicate MAC/IP in reservation dialog (#1070) (#1078)
AustinChangLinksys Jul 8, 2026
9028ec6
fix(dhcp): validate reservations added from Dashboard card (#1067) (#…
AustinChangLinksys Jul 8, 2026
fbe38ca
refactor(dhcp): centralize reservation device-option data source (#1109)
PeterJhongLinksys Jul 9, 2026
3cbee8b
feat(internet): support PPTP, L2TP, and Bridge WAN connection types (…
PeterJhongLinksys Jul 9, 2026
de7d99b
fix(internet): auto-fill and clamp MTU on connection type switch (#10…
PeterJhongLinksys Jul 10, 2026
690bb46
fix(instant-privacy): warn before enabling when devices use private MAC
HankYuLinksys Jul 8, 2026
dbd376a
i18n(instant-privacy): translate private MAC warning; unify Wi-Fi spe…
HankYuLinksys Jul 8, 2026
c4b6adb
refactor(instant-privacy): show private MAC warning on page instead o…
HankYuLinksys Jul 9, 2026
dd711ec
fix: extract inline scripts to external JS for CSP compliance (#1085)
AustinChangLinksys Jul 10, 2026
b001165
fix(internet-settings): remove unnecessary DHCP renew result parsing …
AustinChangLinksys Jul 10, 2026
a79c529
fix(static-routing): block interface change without gateway update (#…
AustinChangLinksys Jul 10, 2026
8a5216e
fix(dhcp): use Hosts.Active for online status indicator (#1034) (#1087)
AustinChangLinksys Jul 10, 2026
418e4f0
fix(nav): use pushNamed for sub-page navigation to preserve back stac…
AustinChangLinksys Jul 10, 2026
905907a
fix(port-forwarding): persist forwarded-port edits in port triggering…
AustinChangLinksys Jul 10, 2026
aa59621
feat(devices): refactor filter to multi-select chips + add device typ…
AustinChangLinksys Jul 10, 2026
8c5b759
fix(local-network): cross-service SET, post-save IP redirect, pool pr…
PeterJhongLinksys Jul 10, 2026
727ed87
fix(dashboard): exit edit mode when navigating away (#1037) (#1089)
AustinChangLinksys Jul 13, 2026
30e78fd
fix(devices): MeshNetwork architecture + fix #1043 #1044 #1047 #1048 …
AustinChangLinksys Jul 13, 2026
b4779dd
refactor: unify TopBar + DiagnosticLoggable state logging + trace lev…
AustinChangLinksys Jul 13, 2026
69eb8f1
fix(l10n): align port forwarding naming with 1.x (#1097)
AustinChangLinksys Jul 13, 2026
cdb3167
fix(auth): unify USP login check so Remote Assistance can open Wi-Fi/…
AustinChangLinksys Jul 14, 2026
8068794
fix(dashboard): upgrade UI Kit to v2.28.0 to clamp traffic curve at b…
AustinChangLinksys Jul 14, 2026
1f71a44
fix(wifi): hide DFS channels when DFS disabled + dedup channel parser…
AustinChangLinksys Jul 14, 2026
b9c33cd
feat(dashboard): show spinner on card toggle during mutation (#1055) …
HankYuLinksys Jul 15, 2026
f2af7bb
fix(pnp): distinguish router read failure from no-internet (#1098) (#…
HankYuLinksys Jul 15, 2026
0cc56ef
feat(fonts): offline CJK subsetting + zero-CDN fallback (12.7MB -> 2.…
AustinChangLinksys Jul 16, 2026
147c3cf
fix(dashboard): hide link-local IPv6 in LAN Information widget (#1129…
AustinChangLinksys Jul 16, 2026
64a7e1b
fix(dashboard): surface WAN global IPv6 instead of link-local fe80:: …
AustinChangLinksys Jul 16, 2026
70c703d
fix(wifi): use OWE token for Enhanced Open security mode (#1073) (#1142)
HankYuLinksys Jul 16, 2026
b7137ea
fix(dashboard/detail): unify IPv6 link-local display with a scope bad…
AustinChangLinksys Jul 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/pr_gate.py",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/pr_gate.py\"",
"timeout": 10
},
{
"type": "command",
"command": "staged=$(git diff --cached --name-only --diff-filter=ACM -- '*.dart'); if [ -n \"$staged\" ]; then if command -v fvm >/dev/null 2>&1; then echo \"$staged\" | xargs fvm dart format; else echo \"$staged\" | xargs dart format; fi; echo \"$staged\" | xargs git add; fi",
"command": "cd \"$CLAUDE_PROJECT_DIR\" || exit 0; staged=$(git diff --cached --name-only --diff-filter=ACM -- '*.dart'); if [ -n \"$staged\" ]; then if command -v fvm >/dev/null 2>&1; then echo \"$staged\" | xargs fvm dart format; else echo \"$staged\" | xargs dart format; fi; echo \"$staged\" | xargs git add; fi",
"timeout": 30,
"statusMessage": "Formatting staged Dart files..."
}
Expand Down
Binary file added assets/fonts/fallback/NotoSansCJKhk.subset.woff2
Binary file not shown.
Binary file added assets/fonts/fallback/NotoSansCJKjp.subset.woff2
Binary file not shown.
Binary file added assets/fonts/fallback/NotoSansCJKkr.subset.woff2
Binary file not shown.
Binary file added assets/fonts/fallback/NotoSansCJKsc.subset.woff2
Binary file not shown.
Binary file added assets/fonts/fallback/NotoSansCJKtc.subset.woff2
Binary file not shown.
85 changes: 63 additions & 22 deletions constitution.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
**Status:** Active
**Context:** Source of Truth for Architectural Discipline
**Ratified:** 2025-12-09
**Last Amended:** 2026-03-20
**Last Amended:** 2026-06-30

## Preamble
This document establishes the immutable principles governing the development process of the Linksys Flutter application. It serves as the architectural DNA of the system, ensuring consistency, simplicity, and quality across all implementations.
Expand Down Expand Up @@ -233,12 +233,11 @@ class DeviceInfo { ... } // generated from Device.DeviceInfo.

**3.3.5: Error Classes**
```dart
// Naming pattern: [Type]Error (final class extending sealed base)
sealed class AuthError { ... }

final class InvalidCredentialsError extends AuthError { ... }
final class NetworkError extends AuthError { ... }
final class StorageError extends AuthError { ... }
// Naming pattern: [Type]Error (final class extending the sealed ServiceError)
// See Article XIII for the unified error hierarchy.
final class InvalidCredentialsError extends ServiceError { ... }
final class NetworkError extends ServiceError { ... }
final class StorageError extends ServiceError { ... }
```

**3.3.6: Result/Response Classes**
Expand Down Expand Up @@ -1028,7 +1027,7 @@ class WifiNotifier extends AsyncNotifier<WifiState> {
|-------|------|------|
| **Service layer** | Any underlying exception (for conversion) | The only place allowed to `catch (e)` |
| **Provider layer** | `ServiceError` only | MUST NOT import or catch underlying exceptions |
| **UI layer** | `ServiceError` only | Displays messages mapped from `ServiceError` types |
| **UI layer** | `ServiceError` only | Localizes via the central `localizeServiceError` mapper (Section 13.6) |

**Purpose**:
- **Isolate data layer implementation**: When the underlying protocol changes (e.g., USP → something else), only the Service layer's conversion logic needs updating — Provider and UI layers are unaffected
Expand All @@ -1044,27 +1043,44 @@ class WifiNotifier extends AsyncNotifier<WifiState> {
**Structure**:
```dart
sealed class ServiceError implements Exception {
const ServiceError();
/// Diagnostic raw fault code (firmware 7xxx/9xxx, WASM 9999, codegen 9998…).
/// For logging/debugging only — `null` when there is no code.
final int? code;

/// Raw technical message (firmware text / WASM string). For logging/debugging.
/// Most subtypes derive their UI message from the type alone and ignore this;
/// fallback types like `UnexpectedError` may surface it.
final String? detail;

const ServiceError({this.code, this.detail});
}

// All error types extend ServiceError
final class InvalidAdminPasswordError extends ServiceError {
const InvalidAdminPasswordError();
// All error types extend ServiceError. Most carry no extra fields — the type
// itself is the semantic. They pass code/detail through to the base.
final class ResourceNotFoundError extends ServiceError {
const ResourceNotFoundError({super.code, super.detail});
}

final class InvalidResetCodeError extends ServiceError {
final int? attemptsRemaining; // Can carry additional information
const InvalidResetCodeError({this.attemptsRemaining});
final class NetworkError extends ServiceError {
const NetworkError({super.code, super.detail});
}

// Fallback for unmapped errors — the one type whose UI message can't be derived
// from the type alone, so it may surface `detail`.
final class UnexpectedError extends ServiceError {
final Object? originalError;
final String? message;
const UnexpectedError({this.originalError, this.message});
const UnexpectedError({this.originalError, super.code, super.detail});
}
```

**Adding Error Types**: To add new error types, define them in `service_error.dart` following the `[ErrorType]Error` naming convention.
**`code` / `detail` are diagnostic only**: they carry firmware/WASM technical
context for logging and are NOT shown to users — the UI derives a localized
message from the subtype (Section 13.6). `UnexpectedError` is the sole exception.

**Adding Error Types**: define them in `service_error.dart` following the
`[ErrorType]Error` naming convention. Because `ServiceError` is `sealed`, the
central UI mapper (Section 13.6) emits a compile-time warning until the new
subtype is given a localization.

---

Expand Down Expand Up @@ -1136,9 +1152,9 @@ Future<void> updatePassword(String newPassword) async {
try {
final svc = ref.read(wifiServiceProvider);
await svc.updatePassword(newPassword);
} on InvalidAdminPasswordError {
// ✅ Handle known ServiceError subtype
state = AsyncError(const InvalidAdminPasswordError(), StackTrace.current);
} on InvalidInputError {
// ✅ Handle a known ServiceError subtype specially
state = AsyncError(const InvalidInputError(), StackTrace.current);
} on ServiceError catch (e) {
// ✅ Handle other ServiceErrors
state = AsyncError(e, StackTrace.current);
Expand All @@ -1156,6 +1172,7 @@ import 'package:privacy_gui/core/errors/service_error.dart';

// performFetch: catch ServiceError → return (null, errorStatus)
// Do NOT rethrow — the mixin's fetch() handles null settings gracefully.
// Store the TYPED ServiceError in state (NOT '$e') so the View can localize it.
@override
Future<(DmzSettings?, DmzStatus?)> performFetch({
bool forceRemote = false,
Expand All @@ -1166,7 +1183,7 @@ Future<(DmzSettings?, DmzStatus?)> performFetch({
return (settings, status);
} on ServiceError catch (e) {
logger.e('[USP][DMZ] Fetch failed', error: e);
return (null, DmzStatus(isLoading: false, errorMessage: '$e'));
return (null, DmzStatus(isLoading: false, error: e)); // typed, not '$e'
}
}

Expand Down Expand Up @@ -1232,6 +1249,30 @@ All USP error parsing and `ServiceError` mapping is centralized in a single util

---

**Section 13.6: UI Layer Error Display**

The UI layer is the **only** place that turns a `ServiceError` into a user-facing
string, and it does so through one central mapper — never by stringifying the error.

**Rules**:
- **Localize via `localizeServiceError(context, error)`** — the single mapper that
switches on the sealed `ServiceError` and returns a localized message. Never show
`'$e'`, `error.toString()`, `code`, or `detail` to the user (those are diagnostic).
- **Fetch failure** → render the shared `ServiceErrorView` (it localizes internally).
- **Save failure** → `showFailedSnackBar(context, localizeServiceError(context, e))`.
- **Adding a subtype** requires adding its localization to the mapper (the `sealed`
switch enforces this at compile time) plus an ARB key.

**Files**: `lib/components/localizations/service_error_localizations.dart` (mapper),
`lib/components/views/service_error_view.dart` (shared fetch-failure widget).

> **Full implementation guidance** — per-layer patterns, what to show vs. hide,
> batch-failure handling, and a pre-PR checklist — lives in
> `doc/error-handling/error-handling-implementation-guide.md`.
> This Constitution states the principle; that guide is the how-to.

---

## Article XIV: Layout Composition Patterns

**Section 14.1: Definition and Scope**
Expand Down
20 changes: 20 additions & 0 deletions doc/error-handling/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Error Handling & Localization

Documentation for USP error handling and error-message localization. One through-line: **how an error flows from firmware all the way to the UI, and how to implement error handling and achieve localization by following the existing patterns**.

## Two documents

| Document | Answers | When to read |
|---|---|---|
| 📘 [**Implementation Guide**](error-handling-implementation-guide.md)<br>`error-handling-implementation-guide.md` | **"How to do it"** — when adding a USP feature page: how to write error handling across the Service / Provider / View layers, what to show, what not to show, how to localize, and a pre-PR checklist | Read before you start implementing |
| 📗 [**Round-trip Reference**](usp-error-handling-reference.md)<br>`usp-error-handling-reference.md` | **"Why"** — the full round trip of an error from firmware through WASM / codegen to the UI, the data format at each layer, an exhaustive list of error sources / forms, the difference between 9999 / 7xxx / 9xxx / 9998, and the cause of the two paths | When you're confused or need to investigate a root cause |

> **Suggested reading order**: read the **Implementation Guide** first (enough to write 80% of cases by following it). When you need to understand "why fetch and save have different error forms" or "how 9999 differs from 7xxx", then turn to the **Round-trip Reference**.

## Source of the existing patterns

The cross-cutting refactor of the error handling pipeline is in **PR #953** (`feat(l10n): centralize error message localization for USP features`). Every pattern in the Implementation Guide reflects the codebase as of after PR #953.

## Known, not yet fixed

- **GET 9999→9998 bug** (Round-trip Reference §2.5): a GET connection failure (9999) is disguised as an "invalid input" error (9998) at the transport layer. It is independent of localization and needs a separate fix — otherwise, no matter how good the l10n is, a GET connection failure will still be shown as "invalid input". Also summarized in the Implementation Guide §7 "Known limitations".
Loading