Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
6997f67
feat: operator-free per-file quiescence-driven NAS sync
Xander-git May 22, 2026
e39e75d
feat: TEST_-prefix mode for identifiable NAS test runs; GUI polish
Xander-git May 26, 2026
056aa91
docs: spec for rclone-only NAS sync with password auth
Xander-git May 27, 2026
0dab3fd
feat(sync): Phase 1 - rclone-only transports + password env injection
Xander-git May 27, 2026
316ca84
fix(sync): Phase 1 review followups - keyring exceptions + missing tests
Xander-git May 27, 2026
e721830
feat(sync): Phase 2 - verifier collapse + Slot A SHA capture
Xander-git May 27, 2026
7b3233d
fix(sync): Phase 2 review followups - docstrings, stub cleanup, Slot …
Xander-git May 27, 2026
b9b567d
feat: setup gate + probe for NAS credentials (Phase 3)
Xander-git May 28, 2026
aeafa88
fix: Phase 3 review followups
Xander-git May 28, 2026
0525dfe
feat: Settings NAS-credentials section + setup banner (Phase 4)
Xander-git May 28, 2026
4222ee5
fix: Phase 4 review followups
Xander-git May 28, 2026
5d49a33
test: e2e/integration regression sweep + password Docker fixture (Pha…
Xander-git May 28, 2026
e1a8dd1
fix: Phase 5 review followups (Docker SMB idempotency)
Xander-git May 28, 2026
8ab8e2e
refactor: dedupe CheckResult->VerifyResult translation (simplify pass)
Xander-git May 28, 2026
d3e89f7
fix: equipment probe must target the rclone remote root with a colon
Xander-git May 28, 2026
60a1faf
fix: install iproute2 in NAS fixture images so the healthcheck works
Xander-git May 28, 2026
608b46d
fix: main-page setup banner must mirror the real setup-state evaluator
Xander-git May 28, 2026
c0e35d8
docs: spec for opt-in staging_root (Approach A)
Xander-git May 29, 2026
85de7a3
feat: live config reload + opt-in staging_root
Xander-git May 29, 2026
013d06f
docs: remaining-work spec + tracked task checklist
Xander-git May 29, 2026
a21ebb4
feat(controller): inject real ReadmeGenerator (T1)
claude May 29, 2026
a1b2dcf
fix(controller): README system.project is the LIMS short id (T1 review)
claude May 29, 2026
95090f8
feat(ui): live wizard phase progress via controller subscribe (T2)
claude May 29, 2026
b89e849
feat(ui): render the Operations panel from the toolbar + footer (T3)
claude May 29, 2026
ab11238
feat(ui): cancel via §9.4 Discard/Keep dialog + §9.6 creation lock (T4)
claude May 29, 2026
bc79962
feat(ui): plugin INPUT_REQUIRED escalation dialog (T5)
claude May 29, 2026
4bc84c9
feat(settings): operators + content-scan chip editors (T7, T10)
claude May 29, 2026
80d78d0
fix(ui): address Phase 2 code review (T2-T5)
claude May 29, 2026
6b62950
feat(settings): wire autostart, quit, and tray-status (T8, T9, T11)
claude May 29, 2026
e39e6ba
feat(ui): real Problems counts + last-audit footer (T6)
claude May 29, 2026
593f988
chore: offline-catalogue treat-as-absent + delete stale registry comm…
claude May 29, 2026
23c3102
fix(ui): address Phase 3 code review
claude May 29, 2026
aa82cb7
docs(lims): fix stale OfflineCatalogue docstring (Phase 4 review)
claude May 29, 2026
28e8f0c
refactor: simplify pass (reuse/dedup/altitude cleanups)
claude May 29, 2026
e0ca6c7
style: apply ruff format + drop stale settings docstring line
claude May 29, 2026
b81f575
Merge pull request #16 from exfab/claude/nice-gauss-ZBnL6
Xander-git May 29, 2026
2eec1b8
docs: spec for rclone.conf-based NAS sync
Xander-git May 29, 2026
d10ce32
docs: implementation plan for rclone.conf-based NAS sync
Xander-git May 29, 2026
88956a3
feat(config): add nas: block (remote, base_root, perf, bandwidth)
Xander-git May 29, 2026
f2b6e47
test(config): nas: block round-trips through the loader
Xander-git May 29, 2026
faefc54
fix(config): resolve lint and test regressions from nas: block addition
Xander-git May 29, 2026
7e15499
test(config): assert nas: block survives save via re-parse
Xander-git May 29, 2026
74d1447
feat(transport): rclone driver accepts --config/--transfers/--checkers
Xander-git May 29, 2026
5fc3691
feat(transport): add read-only RcloneDriver.lsjson
Xander-git May 29, 2026
aae28f9
feat(transport): add RcloneDriver.listremotes
Xander-git May 29, 2026
ab6229d
test(transport): use pytest.mark.asyncio convention for driver tests
Xander-git May 29, 2026
c89cf84
feat(sync): add lsjson manifest parser
Xander-git May 29, 2026
d30d07a
feat(sync): lsjson reconcile + cleanup hash-gate (named-remote NAS sync)
Xander-git May 29, 2026
2535db9
test(sync): migrate NAS-sync tests to lsjson reconcile + cleanup hash…
Xander-git May 29, 2026
19d762d
test(sync): cover cleanup existence-probe-fail branch; fix stale docs…
Xander-git May 29, 2026
a7f9d87
refactor(enums): NAS setup state is remote-availability not credential
Xander-git May 29, 2026
74f8fe3
feat(setup): gate on rclone remote availability, not keyring password
Xander-git May 29, 2026
2a62b9a
feat(tray): equipment probe + setup gate use named rclone remote
Xander-git May 29, 2026
8350673
feat(api): setup status reports rclone-remote availability
Xander-git May 29, 2026
199379a
test(e2e): park NAS-credential flow pending rclone-remote migration (…
Xander-git May 29, 2026
98d1a4e
feat: relax EquipmentConfig validator — nas-mode no longer requires t…
Xander-git May 29, 2026
80e9606
feat: migrate NAS UI/plumbing from keyring credentials to rclone remote
Xander-git May 29, 2026
9d67c32
test: migrate NAS UI tests to the rclone-remote model (Phase 6, Task E)
Xander-git May 29, 2026
12547b1
refactor(config): remove per-equipment transport blocks (clean break)
Xander-git May 29, 2026
56c178f
refactor(transport): remove credential injection + NAS keyring helper
Xander-git May 29, 2026
d32fd21
feat(config): orchestrator staging uses a named rclone remote
Xander-git May 29, 2026
87a69b1
feat(sync): stage-mode equipment push via rclone staging remote
Xander-git May 29, 2026
0fccf9b
test(integration): configure nas: remote in full-flow fixtures for th…
Xander-git May 29, 2026
8104a6b
test(fixtures): stub_rclone listremotes verb + --config tolerance
Xander-git May 29, 2026
819f2cc
test(e2e): refresh NAS settings testids/state for rclone-remote flow
Xander-git May 29, 2026
775c325
test(docker): add rclone.conf for dockerised SFTP/SMB integration
Xander-git May 29, 2026
44efbb6
test(sync): assert bandwidth cap comes from the nas: block
Xander-git May 29, 2026
9429955
test(sync): fix import order and unused variable (ruff)
Xander-git May 29, 2026
9524a53
test(e2e): migrate equipment-wizard flows + ux catalog off removed tr…
Xander-git May 29, 2026
3ad6e32
docs: rclone.conf setup guide + spec/README updates for named-remote …
Xander-git May 29, 2026
bc123fa
docs: fix stale rsync-driver + tempfile claims in sync spec §7
Xander-git May 29, 2026
179e9b0
refactor(sync): unify per-equipment remote/driver resolution
Xander-git May 29, 2026
2ac5cd4
fix(setup): restore defensive [] fallback in setup_state_missing
Xander-git May 29, 2026
1a14796
test: ruff-format suite and raise NAS-sync coverage above the 91% gate
Xander-git May 29, 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
  •  
  •  
  •  
97 changes: 97 additions & 0 deletions .agents/skills/source-command-code-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
name: "source-command-code-review"
description: "Code review a pull request"
---

# source-command-code-review

Use this skill when the user asks to run the migrated source command `code-review`.

## Command Template

Provide a code review for the given pull request.

To do this, follow these steps precisely:

1. Use a Haiku agent to check if the pull request (a) is closed, (b) is a draft, (c) does not need a code review (eg. because it is an automated pull request, or is very simple and obviously ok), or (d) already has a code review from you from earlier. If so, do not proceed.
2. Use another Haiku agent to give you a list of file paths to (but not the contents of) any relevant AGENTS.md files from the codebase: the root AGENTS.md file (if one exists), as well as any AGENTS.md files in the directories whose files the pull request modified
3. Use a Haiku agent to view the pull request, and ask the agent to return a summary of the change
4. Then, launch 5 parallel Sonnet agents to independently code review the change. The agents should do the following, then return a list of issues and the reason each issue was flagged (eg. AGENTS.md adherence, bug, historical git context, etc.):
a. Agent #1: Audit the changes to make sure they compily with the AGENTS.md. Note that AGENTS.md is guidance for Codex as it writes code, so not all instructions will be applicable during code review.
b. Agent #2: Read the file changes in the pull request, then do a shallow scan for obvious bugs. Avoid reading extra context beyond the changes, focusing just on the changes themselves. Focus on large bugs, and avoid small issues and nitpicks. Ignore likely false positives.
c. Agent #3: Read the git blame and history of the code modified, to identify any bugs in light of that historical context
d. Agent #4: Read previous pull requests that touched these files, and check for any comments on those pull requests that may also apply to the current pull request.
e. Agent #5: Read code comments in the modified files, and make sure the changes in the pull request comply with any guidance in the comments.
5. For each issue found in #4, launch a parallel Haiku agent that takes the PR, issue description, and list of AGENTS.md files (from step 2), and returns a score to indicate the agent's level of confidence for whether the issue is real or false positive. To do that, the agent should score each issue on a scale from 0-100, indicating its level of confidence. For issues that were flagged due to AGENTS.md instructions, the agent should double check that the AGENTS.md actually calls out that issue specifically. The scale is (give this rubric to the agent verbatim):
a. 0: Not confident at all. This is a false positive that doesn't stand up to light scrutiny, or is a pre-existing issue.
b. 25: Somewhat confident. This might be a real issue, but may also be a false positive. The agent wasn't able to verify that it's a real issue. If the issue is stylistic, it is one that was not explicitly called out in the relevant AGENTS.md.
c. 50: Moderately confident. The agent was able to verify this is a real issue, but it might be a nitpick or not happen very often in practice. Relative to the rest of the PR, it's not very important.
d. 75: Highly confident. The agent double checked the issue, and verified that it is very likely it is a real issue that will be hit in practice. The existing approach in the PR is insufficient. The issue is very important and will directly impact the code's functionality, or it is an issue that is directly mentioned in the relevant AGENTS.md.
e. 100: Absolutely certain. The agent double checked the issue, and confirmed that it is definitely a real issue, that will happen frequently in practice. The evidence directly confirms this.
6. Filter out any issues with a score less than 80. If there are no issues that meet this criteria, do not proceed.
7. Use a Haiku agent to repeat the eligibility check from #1, to make sure that the pull request is still eligible for code review.
8. Finally, use the gh bash command to comment back on the pull request with the result. When writing your comment, keep in mind to:
a. Keep your output brief
b. Avoid emojis
c. Link and cite relevant code, files, and URLs

Examples of false positives, for steps 4 and 5:

- Pre-existing issues
- Something that looks like a bug but is not actually a bug
- Pedantic nitpicks that a senior engineer wouldn't call out
- Issues that a linter, typechecker, or compiler would catch (eg. missing or incorrect imports, type errors, broken tests, formatting issues, pedantic style issues like newlines). No need to run these build steps yourself -- it is safe to assume that they will be run separately as part of CI.
- General code quality issues (eg. lack of test coverage, general security issues, poor documentation), unless explicitly required in AGENTS.md
- Issues that are called out in AGENTS.md, but explicitly silenced in the code (eg. due to a lint ignore comment)
- Changes in functionality that are likely intentional or are directly related to the broader change
- Real issues, but on lines that the user did not modify in their pull request

Notes:

- Do not check build signal or attempt to build or typecheck the app. These will run separately, and are not relevant to your code review.
- Use `gh` to interact with Github (eg. to fetch a pull request, or to create inline comments), rather than web fetch
- Make a todo list first
- You must cite and link each bug (eg. if referring to a AGENTS.md, you must link it)
- For your final comment, follow the following format precisely (assuming for this example that you found 3 issues):

---

### Code review

Found 3 issues:

1. <brief description of bug> (AGENTS.md says "<...>")

<link to file and line with full sha1 + line range for context, note that you MUST provide the full sha and not use bash here, eg. https://github.com/anthropics/Codex/blob/1d54823877c4de72b2316a64032a54afc404e619/README.md#L13-L17>

2. <brief description of bug> (some/other/AGENTS.md says "<...>")

<link to file and line with full sha1 + line range for context>

3. <brief description of bug> (bug due to <file and code snippet>)

<link to file and line with full sha1 + line range for context>

🤖 Generated with [Codex](https://Codex.ai/code)

<sub>- If this code review was useful, please react with 👍. Otherwise, react with 👎.</sub>

---

- Or, if you found no issues:

---

### Code review

No issues found. Checked for bugs and AGENTS.md compliance.

🤖 Generated with [Codex](https://Codex.ai/code)

- When linking to code, follow the following format precisely, otherwise the Markdown preview won't render correctly: https://github.com/anthropics/Codex-cli-internal/blob/c21d3c10bc8e898b7ac1a2d745bdc9bc4e423afe/package.json#L10-L15
- Requires full git sha
- You must provide the full sha. Commands like `https://github.com/owner/repo/blob/$(git rev-parse HEAD)/foo/bar` will not work, since your comment will be directly rendered in Markdown.
- Repo name must match the repo you're code reviewing
- # sign after the file name
- Line range format is L[start]-L[end]
- Provide at least 1 line of context before and after, centered on the line you are commenting about (eg. if you are commenting about lines 5-6, you should link to `L4-7`)
49 changes: 49 additions & 0 deletions .codex/agents/code-simplifier.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
description = "Simplifies and refines code for clarity, consistency, and maintainability while preserving all functionality. Focuses on recently modified code unless instructed otherwise."
developer_instructions = """
You are an expert code simplification specialist focused on enhancing code clarity, consistency, and maintainability while preserving exact functionality. Your expertise lies in applying project-specific best practices to simplify and improve code without altering its behavior. You prioritize readable, explicit code over overly compact solutions. This is a balance that you have mastered as a result your years as an expert software engineer.

You will analyze recently modified code and apply refinements that:

1. **Preserve Functionality**: Never change what the code does - only how it does it. All original features, outputs, and behaviors must remain intact.

2. **Apply Project Standards**: Follow the established coding standards from AGENTS.md including:

- Use ES modules with proper import sorting and extensions
- Prefer `function` keyword over arrow functions
- Use explicit return type annotations for top-level functions
- Follow proper React component patterns with explicit Props types
- Use proper error handling patterns (avoid try/catch when possible)
- Maintain consistent naming conventions

3. **Enhance Clarity**: Simplify code structure by:

- Reducing unnecessary complexity and nesting
- Eliminating redundant code and abstractions
- Improving readability through clear variable and function names
- Consolidating related logic
- Removing unnecessary comments that describe obvious code
- IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions
- Choose clarity over brevity - explicit code is often better than overly compact code

4. **Maintain Balance**: Avoid over-simplification that could:

- Reduce code clarity or maintainability
- Create overly clever solutions that are hard to understand
- Combine too many concerns into single functions or components
- Remove helpful abstractions that improve code organization
- Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners)
- Make the code harder to debug or extend

5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.

Your refinement process:

1. Identify the recently modified code sections
2. Analyze for opportunities to improve elegance and consistency
3. Apply project-specific best practices and coding standards
4. Ensure all functionality remains unchanged
5. Verify the refined code is simpler and more maintainable
6. Document only significant changes that affect understanding

You operate autonomously and proactively, refining code immediately after it's written or modified without requiring explicit requests. Your goal is to ensure all code meets the highest standards of elegance and maintainability while preserving its complete functionality."""
name = "code-simplifier"
23 changes: 23 additions & 0 deletions .codex/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[mcp_servers.context7]
args = [
"-y",
"@upstash/context7-mcp",
]
command = "npx"

[mcp_servers.playwright]
args = [
"-y",
"@playwright/mcp@latest",
"--headless",
]
command = "npx"

[mcp_servers.playwright.tools.browser_navigate]
approval_mode = "approve"

[mcp_servers.playwright.tools.browser_evaluate]
approval_mode = "approve"

[mcp_servers.playwright.tools.browser_click]
approval_mode = "approve"
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ state/
*.tmp
.tmp/

# NAS emulator (tests/docker/) — keep the layout, ignore generated state
tests/docker/nas-data/*
!tests/docker/nas-data/.gitkeep

# Local secrets / env (never commit)
.env
.env.*
# ...but the committed templates carry no secrets.
!.env.example
!tests/docker/.env.example
*.secret

# pre-commit cache
.pre-commit-cache/

Expand Down
2 changes: 1 addition & 1 deletion .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp@latest", "--headless"]
"args": ["-y", "@playwright/mcp@latest"]
}
}
}
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,24 @@ workflow additionally verifies the LIMS client against a live upstream
[`mcnaughtonadm/exlab`](https://gitlab.com/mcnaughtonadm/exlab)
container weekly and on every merge to `main`.

## NAS sync setup

ExLab-Wizard syncs runs to your NAS via [rclone](https://rclone.org/) named
remotes. The app never stores NAS passwords — you configure the remote once
with `rclone config` and wire the remote name into `config.yaml`:

```yaml
nas:
remote: "lab-nas" # name you gave the remote in rclone config
base_root: "lab" # path on the remote under which equipment folders live
```

Step-by-step instructions (SFTP and SMB walkthroughs, performance tuning,
tray-service caveats): **[`docs/setup/rclone-remote-setup.md`](docs/setup/rclone-remote-setup.md)**.

Settings → **NAS Remote** → **Test connection** verifies the remote is
reachable before you start syncing.

## Documentation

The published Sphinx site is at
Expand All @@ -120,5 +138,6 @@ Local sources:
- Operator-facing user guide: `docs/source/user_guide/` (rendered via
Sphinx; `make -C docs html` writes the site to `docs/build/html/`).
- Plugin authoring guide: `docs/source/plugin_guide/`.
- Setup guides: `docs/setup/` (rclone remote setup, etc.).
- Design specs: `design_specs/` (the authoritative source for
capability scope, interfaces, and wire contracts).
4 changes: 2 additions & 2 deletions design_specs/ExLab-Wizard_Design_Spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Defines the class-based `Plugin` contract (lifecycle hooks: `validate_variables`

Moved to [[design_spec_sections/07_Sync_and_Database_Integration]].

Covers NAS mirror behavior, the LIMS record schema, and the Pre-Sync Gate that consumes validator findings to block flagged runs from sync.
Covers NAS sync via operator-managed rclone named remotes (`RcloneDriver`), the two-tier verify model (cheap `rclone lsjson` reconcile after each push + `rclone check --download` hash gate at cleanup), bandwidth limiting, the LIMS record schema, and the Pre-Sync Gate that consumes validator findings to block flagged runs from sync.

---

Expand All @@ -112,7 +112,7 @@ Top-level error handling principles plus the §8.1 path validation rules: unreso

Moved to [[design_spec_sections/09_Configuration_File]].

Full annotated `config.yaml` example: paths, database, README defaults, equipment registry (with transports and completeness signals), sync, and orchestrator settings.
Full annotated `config.yaml` example: paths, database, README defaults, equipment registry (id/label/local_root/nas_root/sync_mode only — no per-equipment transport block), the top-level `nas:` block (remote name, base_root, rclone_config_path, mtime_tolerance_s, perf, bandwidth), sync, and orchestrator settings (including staging_remote/staging_base_root for the stage-mode hop).

---

Expand Down
23 changes: 6 additions & 17 deletions design_specs/ExLab-Wizard_Frontend_Spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ When the operator tries to advance with an empty core field, the error message m

The Settings dialog is the operator's surface for configuring everything in `config.yaml` and the OS-keyring credentials it references. User capability: "Configure Equipment, Paths, and Integrations" (User Interaction Spec Section 3.6); backend-side schemas live in Design Spec §9.

**Important: no plaintext credentials anywhere in this dialog.** All secrets (LIMS password, the rare per-equipment NAS HTTP-basic password) are managed via the OS keyring (Design Spec §7.4); the dialog never displays a stored secret.
**Important: no plaintext credentials anywhere in this dialog.** The only secret the app manages is the LIMS password, stored in the OS keyring (Design Spec §7.4); the dialog never displays a stored secret. NAS credentials live entirely in the operator's `rclone.conf` (Design Spec §7.1.8); the app only stores and displays the remote name.

### 7.1 Modality

Expand Down Expand Up @@ -852,7 +852,7 @@ Switching sections does NOT discard a section's edits — the working copy persi

#### 7.4.1 Credential field

Used for the LIMS password (§7.6) and per-equipment HTTP-basic NAS passwords where the configured transport requires one (§7.7.2). Never displays a stored value.
Used for the LIMS password (§7.6). Never displays a stored value.

A credential row has two resting states and one transient state:

Expand Down Expand Up @@ -941,27 +941,16 @@ Modal-on-modal sub-dialog. Scrollable single-column form. Primary button: **Done
**Storage group**

- **Local root.** Directory picker; backs `local_root`.
- **NAS root** (display value). Text input; backs `nas_root`. Helper: *"Display path shown in the UI. The actual transport target is configured below."*

**Transport group**

- **Transport type.** Radio: `rclone` / `rsync_ssh`. Switching this resets the conditional fields below to their defaults and dirties the form.
- For `rclone`:
- **Remote name.** Single-line input. Helper: *"Remote name from `rclone.conf`. To set up a new remote, run `rclone config` from a terminal — this app does not edit `rclone.conf`."*
- **Remote path.** Single-line input.
- For `rsync_ssh`:
- **SSH target.** Single-line input (e.g. `labuser@nas01.lab.example`).
- **SSH key path.** File picker, default `~/.ssh/id_ed25519`. Helper: *"Password authentication is not supported. The key file must be present and have safe permissions."*
- **Remote path.** Single-line input.
- **(Optional) NAS HTTP-basic password.** Credential field (§7.4.1), suppressed by default and shown only when the configured transport requires one.
- **NAS root** (display value). Text input; backs `nas_root`. Helper: *"Display path shown in the browse view. The actual sync target is composed from `nas.remote` + `nas.base_root` + equipment ID (see the NAS Remote section)."*
- **Sync mode.** Radio: `nas` / `stage`. `nas` syncs runs directly to the NAS remote; `stage` pushes to the orchestrator staging remote.

**Bandwidth group.** See §7.7.3.

**[Test connection]** button at the bottom of the sub-dialog. For `rclone`, runs `rclone lsd <remote>:<path>` against the working-copy values. For `rsync_ssh`, opens an SSH connection and runs `ls <remote_path>`. Result panel per §7.4.2.
**[Test connection]** is in the top-level **NAS Remote** section (not per-equipment). It runs `rclone about <remote>:` against the global `nas.remote` value and displays the result per §7.4.2.

#### 7.7.3 Bandwidth schedule editor

Inside the equipment sub-dialog, in the Bandwidth group. Backs `transport.bandwidth` (Design Spec §9).
Inside the Settings NAS Remote section. Backs `nas.bandwidth` (Design Spec §9).

**Mode selector** (radio):

Expand Down
Loading
Loading