Skip to content

Commit 13085d2

Browse files
committed
[docs] freeze official examples branch discovery
Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent 442fa7f commit 13085d2

5 files changed

Lines changed: 265 additions & 11 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# ADR-0042 — Prefill the official examples repository and discover branches
2+
3+
- Status: **Accepted**
4+
- Date: 2026-08-27
5+
- Extends: [ADR-0040](0040-sha-pinned-connected-github-import.md)
6+
- Extends: [ADR-0041](0041-separate-official-examples-repository.md)
7+
8+
## Context
9+
10+
The connected GitHub importer accepts any canonical public repository URL and
11+
an optional branch, tag, or commit. That generic input is powerful, but an
12+
empty repository field and free-form ref field make the first import harder
13+
than necessary for a novice. The separately governed official collection now
14+
has a stable public identity at <https://github.com/PyBLE-dev/examples>, so the
15+
app can offer that identity as a starting value without coupling to the
16+
maintainer's local checkout or granting the repository additional trust.
17+
18+
GitHub exposes public branch names through a paginated, unauthenticated REST
19+
endpoint. Branch discovery is mutable navigation metadata, not an immutable
20+
source identity: a selected name must still be resolved again and pinned to a
21+
full commit SHA before any tree or blob is browsed. Unauthenticated requests
22+
are also rate- and resource-limited, so “all branches” needs a complete,
23+
explicitly bounded result rather than an unbounded or silently partial list.
24+
25+
ADR-0040 deliberately preserved explicit tag and commit refs. Removing those
26+
inputs would break existing advanced and reproducible-import workflows.
27+
28+
## Decision
29+
30+
1. **Prefill, but keep the repository editable.** Every newly opened GitHub
31+
import action initializes **Public repository URL** to
32+
`https://github.com/PyBLE-dev/examples`. The user may replace it with any
33+
other canonical public GitHub repository root accepted by the existing
34+
parser. Reopening the action starts again from the official value; the app
35+
does not persist a previous repository in this increment.
36+
37+
2. **Treat the official value as ordinary untrusted input.** The prefill is a
38+
local UI convenience, not an allowlist, trust signal, compatibility claim,
39+
build input, or link to `/Users/vyv/Working/SciLabPro/PyBLE-Examples`. It
40+
crosses the same exact-host network boundary and the same SHA pinning,
41+
object validation, size, UTF-8, overwrite, session, and board-write gates as
42+
every custom repository. It causes no automatic file selection, source
43+
fetch, import, editor open, Save, or Run.
44+
45+
3. **Make branches the normal ref workflow.** The normal control is a
46+
searchable, keyboard- and screen-reader-operable branch-only chooser. The
47+
app reads the repository's reported default branch, places it first, marks
48+
it as the default, and initially selects it. Remaining exact branch names
49+
use deterministic code-point order. Branch names are opaque technical
50+
values and are never localized or rewritten.
51+
52+
4. **Retain advanced explicit refs.** A clearly labelled advanced action
53+
switches to manual branch, tag, or commit entry. The existing ref
54+
validation and blank-default behavior remain available there. Switching
55+
modes or changing the manual ref invalidates any pinned snapshot,
56+
navigation, selection, review, and overwrite consent. This preserves
57+
ADR-0040's compatibility while keeping the default surface novice-focused.
58+
59+
5. **Discover only after a user opens the importer.** App launch and ordinary
60+
Files navigation make no GitHub request. Opening the explicit **Import
61+
examples from GitHub** action may automatically discover branches for the
62+
prefilled repository. Editing the URL immediately cancels and clears that
63+
catalog and every dependent snapshot. It does not request on each
64+
keystroke; the user explicitly loads or refreshes branches for an edited
65+
canonical URL.
66+
67+
6. **Return a complete bounded catalog or none.** Discovery first reads public
68+
repository metadata for `default_branch`, then serially requests
69+
`GET /repos/{owner}/{repo}/branches?per_page=100&page=N` without a
70+
`protected` filter. It accepts at most 512 branch names over at most six
71+
pages, at most 512 KiB per branch page, and at most 2 MiB for all branch-page
72+
bodies. A 513th branch, another next page after the sixth, duplicate or
73+
invalid name, malformed page, or exceeded byte bound returns a typed failure
74+
and publishes no partial catalog. An empty catalog is displayed honestly
75+
and cannot be browsed in branch mode.
76+
77+
7. **Keep pagination inside the exact-host boundary.** The client validates
78+
GitHub's `Link` pagination metadata, including exact HTTPS host, repository
79+
branch path, `per_page`, and strictly increasing `page`. It never directly
80+
follows a response-provided URL; it constructs the next exact
81+
`api.github.com` request from validated local values. Each page uses the
82+
existing absolute request deadline, cancellation, response-body, error,
83+
and rate-limit rules.
84+
85+
8. **Pin after selection.** A branch catalog and any commit SHA contained in a
86+
branch-list response are advisory and moving. Browse passes only the chosen
87+
name to the existing resolution operation, requires the returned full
88+
commit and root-tree SHAs, displays the pinned commit, and derives every
89+
subsequent tree/blob read from that immutable snapshot.
90+
91+
9. **Share cancellation and rate state.** Branch loading is a distinct visible
92+
network phase. Repository edits, refresh, mode changes, cancellation, and
93+
dismissal advance the presentation generation and cancel any active
94+
discovery. Late pages cannot repopulate the chooser. A GitHub-supplied
95+
positive rate-limit delay gates branch load/refresh and all other
96+
network-producing actions on the surface under ADR-0040's existing rule.
97+
98+
10. **Specify and test the complete interaction.** ARB owns branch labels,
99+
loading, refresh, default, empty, advanced-mode, and bound/error text.
100+
Automated tests cover the editable prefill, custom-repository parity,
101+
initial/default selection, branch-only results, advanced tag/commit
102+
compatibility, bounded pagination, no partial publication, cancellation,
103+
stale-result suppression, rate gating, immutable resolution, compact/wide
104+
layouts, keyboard use, semantics, and 2× text.
105+
106+
## Consequences
107+
108+
- A novice can open the importer and choose an official branch without knowing
109+
Git ref syntax or copying a URL.
110+
- Experienced users retain exact tags and commits for reproducible imports.
111+
- Large or changing repositories fail explicitly instead of showing a partial
112+
list that could be mistaken for all branches.
113+
- Opening the importer uses additional public GitHub requests, but no other app
114+
workflow becomes network-dependent and the existing rate-limit guidance
115+
remains authoritative.
116+
- The official repository remains independently released and untrusted at the
117+
importer boundary; this decision creates no filesystem or build coupling.
118+
119+
## Alternatives considered
120+
121+
- **Remove tags and commits entirely.** Rejected because it breaks the
122+
explicit-ref contract and makes immutable, release-specific imports harder.
123+
- **Request branches after every URL keystroke.** Rejected because it wastes
124+
the unauthenticated request budget and creates avoidable stale-response races.
125+
- **Follow `Link` URLs directly.** Rejected because response-provided URLs must
126+
not widen the exact-host request boundary.
127+
- **Show only the first 100 branches.** Rejected because a partial list would
128+
contradict the selector's “available branches” meaning.
129+
- **Make the official repository trusted or board-aware.** Rejected because
130+
repository ownership is not evidence that source is safe for a particular
131+
board or should execute automatically.
132+
133+
## Related
134+
135+
- [App requirements §4.9](../specifications/App/specs.md#49-github-public-repo-import-libgithub_import--fr-import)
136+
- [App TDD §10](../specifications/App/TDD.md#10-github-import-design-libgithub_import)
137+
- [PRD §9.7](../specifications/prd.md#97-github-public-repo-import-https)
138+
139+
<!-- SPDX-License-Identifier: MIT -->

‎docs/decisions/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@ Each ADR captures one significant decision: its context, the choice, and the con
4545
| [0039](0039-admit-waveshare-single-page-largest-block-transient.md) | Admit the characterized Waveshare single-page largest-block transient with a fixed 98304 floor in a V5 source era, with byte-identity-conditioned evidence carry-forward | Accepted |
4646
| [0040](0040-sha-pinned-connected-github-import.md) | Ship GitHub import first as a SHA-pinned, bounded connected-board subset while durable local files and provenance remain deferred | Accepted |
4747
| [0041](0041-separate-official-examples-repository.md) | Maintain the official user-facing runnable examples in a separately governed repository while PyBLE retains only its importer fixtures and bundled offline examples | Accepted |
48+
| [0042](0042-prefill-official-examples-and-discover-branches.md) | Prefill the editable official examples repository and provide bounded, branch-only discovery while retaining advanced explicit refs | Accepted |
4849

49-
Decided 2026-06-30 (0001–0005); 0006 accepted 2026-07-01; 0007, 0008, 0009 and 0010 accepted 2026-07-02; 0011 accepted 2026-07-03; 0012 accepted 2026-07-25; 0013 accepted 2026-07-27; 0014, 0015, 0016, and 0017 accepted 2026-07-28; 0018–0022 accepted 2026-07-29; 0023 and 0024 accepted 2026-08-01; 0025 and 0026 accepted 2026-08-02; 0027–0029 accepted 2026-08-03; 0030 accepted 2026-08-11; 0031–0033 accepted 2026-08-12; 0034 accepted 2026-08-13; 0035 and 0036 accepted 2026-08-16; 0037 and 0038 accepted 2026-08-20; 0039 accepted 2026-08-21; 0040 accepted 2026-08-22; 0041 accepted 2026-08-25.
50+
Decided 2026-06-30 (0001–0005); 0006 accepted 2026-07-01; 0007, 0008, 0009 and 0010 accepted 2026-07-02; 0011 accepted 2026-07-03; 0012 accepted 2026-07-25; 0013 accepted 2026-07-27; 0014, 0015, 0016, and 0017 accepted 2026-07-28; 0018–0022 accepted 2026-07-29; 0023 and 0024 accepted 2026-08-01; 0025 and 0026 accepted 2026-08-02; 0027–0029 accepted 2026-08-03; 0030 accepted 2026-08-11; 0031–0033 accepted 2026-08-12; 0034 accepted 2026-08-13; 0035 and 0036 accepted 2026-08-16; 0037 and 0038 accepted 2026-08-20; 0039 accepted 2026-08-21; 0040 accepted 2026-08-22; 0041 accepted 2026-08-25; 0042 accepted 2026-08-27.

‎docs/specifications/App/TDD.md‎

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# PyBLE App — Technical Design Document (TDD)
22

3-
Status: **DRAFT** · Owner: project maintainer · Last updated: 2026-08-22
3+
Status: **DRAFT** · Owner: project maintainer · Last updated: 2026-08-27
44

55
## 0. Naming note (acronym clash)
66

@@ -345,20 +345,23 @@ The full design later stores the exact bytes locally as the source of truth and
345345
records provenance; those durable parts remain deferred to A-24/full A-33.
346346

347347
**Public API (implemented connected subset):** an injected `GithubApi` with
348+
`listBranches(locator) → GithubBranchCatalog`,
348349
`resolve(locator) → PinnedRepository`, `listDirectory(repository, treeSha,
349350
path)`, and `fetchFile(repository, entry)`. `githubApiProvider` is the sole
350351
production composition point: it owns and closes the `http.Client` and exposes
351352
only `GithubApi`. `GithubRepositoryClient` implements that board-independent
352-
REST seam. The adaptive view owns ephemeral repository/ref input, lazy
353+
REST seam. The adaptive view owns the editable official repository initial
354+
value, branch/manual-ref mode, ephemeral ref input, lazy
353355
navigation, selection, retry, focus, and visible-step state; it delegates
354356
board review/commit/cancellation to `GithubBoardImporter`. That service owns
355357
safe target derivation, complete-listing conflict snapshots, bounded all-fetch
356358
validation, the session-bound sequential PUT loop, progress/result accounting,
357359
and Files refresh. It knows no widget, BLE transport, editor, or persistence
358360
type.
359361

360-
**Key state:** presentation scope holds the canonical repo locator,
361-
requested/default ref, full pinned commit SHA, current remote-directory stack,
362+
**Key state:** presentation scope holds the canonical repo locator, complete
363+
branch catalog, chosen ref mode/value, full pinned commit SHA, current
364+
remote-directory stack,
362365
lazy entries, folder-local selection, retry/focus data, and visible
363366
browse/review/result state. One `GithubBoardImporter` instance is constructed
364367
per opened action with the captured board `cwd`, stable `Connection` facade,
@@ -692,6 +695,11 @@ data store. The boundary values are:
692695

693696
```dart
694697
record RepositoryLocator(Uri canonicalRoot, String owner, String repo);
698+
record GithubBranchCatalog(
699+
RepositoryLocator locator,
700+
String defaultBranch,
701+
List<String> branchNames,
702+
);
695703
record PinnedRepository(
696704
RepositoryLocator locator,
697705
String requestedRef,
@@ -735,6 +743,11 @@ The fetched `_ImportCandidate` is deliberately a private service value rather
735743
than public/provider state: it exists only inside one bounded `commit()` call
736744
and is released at terminal return.
737745

746+
`GithubBranchCatalog.branchNames` is immutable and contains exact, validated
747+
technical ref values. Discovery may observe moving branch tips, but exposes no
748+
branch-list commit identity because `resolve(locator, ref:)` remains the sole
749+
operation that publishes an immutable browse pin.
750+
738751
`GithubEntryKind.regularFile` is granted only to a Git tree entry with
739752
`type = blob` and mode `100644` or `100755`; a directory is `type = tree` and
740753
mode `040000`. Symlink mode `120000`, submodule `type = commit` / mode `160000`,
@@ -754,6 +767,15 @@ targets the new `cwd`.
754767

755768
### 10.2 Canonical input and immutable snapshot resolution
756769

770+
The presentation owns the local constant
771+
`https://github.com/PyBLE-dev/examples` and initializes each new import action's
772+
repository controller from it. The value is editable and is not fetched from,
773+
or coupled to, the sibling examples checkout. App launch and ordinary Files
774+
navigation never request it. Opening the user-invoked importer may call
775+
`listBranches`; editing the URL clears and cancels the catalog without issuing
776+
a request per keystroke, and an explicit load/refresh retrieves branches for
777+
the new canonical value.
778+
757779
`RepositoryLocator.parse` accepts only HTTPS, exact host `github.com`, default
758780
port, no user-info/query/fragment, and exactly two non-empty decoded path
759781
segments. One trailing slash is removed. `.git`, `tree`/`blob` subpaths,
@@ -793,25 +815,49 @@ rejected above 512 direct entries before the directory model reaches the eager
793815
tablet view. These are hard resource ceilings, not pagination: the subset never
794816
silently presents a partial folder.
795817

818+
Before normal-mode resolution, `listBranches(locator)` reads repository
819+
metadata for `default_branch`, then serially reads the public branches endpoint
820+
with `per_page=100` and increasing `page`. It publishes only a complete
821+
`GithubBranchCatalog`: at most 512 exact names over six pages, 512 KiB per
822+
branch page, and 2 MiB across branch-page bodies. Invalid/duplicate names,
823+
malformed metadata or pagination, a 513th name, or a further page fails without
824+
publishing accumulated names. `Link` metadata is accepted only when its next
825+
URL is HTTPS on exact host `api.github.com`, has the exact repository branches
826+
path and expected page parameters, and advances by one; the returned URL is
827+
never followed directly. The adapter constructs the next request from its
828+
validated locator and page number. The reported default is first and selected;
829+
remaining branches use `String.compareTo`. An empty catalog is displayed and
830+
cannot Browse in branch mode.
831+
832+
The normal ref control is a searchable branch-only chooser. A localized
833+
advanced action exposes the retained manual branch/tag/commit field, including
834+
ADR-0040's blank-default behavior. Mode/value changes invalidate the pinned
835+
snapshot and all dependent state. In both modes the chosen value passes to
836+
`resolve`; catalog metadata never substitutes for full commit/root-tree
837+
resolution.
838+
796839
### 10.3 Lazy browse, selection, and adaptive state
797840

798841
The user-visible lifecycle remains:
799842

800843
```text
801-
enterRepository → resolving → browsing → selecting → reviewing
844+
enterRepository → loadingBranches → choosingRef → resolving → browsing
845+
→ selecting → reviewing
802846
→ awaitingOverwrite → fetching → recheckingBoard → uploading
803847
→ complete | partial | cancelled | failed
804848
```
805849

806850
The implementation does not introduce a second Riverpod workflow controller.
807851
The adaptive `StatefulWidget` owns presentation-only state: a
808852
`browse | review | result` surface step,
809-
`resolving | loadingDirectory | checkingBoardTargets` operation phase,
853+
`loadingBranches | resolving | loadingDirectory | checkingBoardTargets`
854+
operation phase,
810855
directory stack, selection, failure/retry/focus data, and the current
811856
progress/result. Resolve/list operations capture the view's increasing
812-
epoch and a `GithubCancellation`; cancel, ref refresh, directory change, or
813-
disposal advances that epoch, and a late completion cannot repopulate the
814-
view. `GithubBoardImporter` separately owns its fetch/commit generation,
857+
epoch and a `GithubCancellation`; repository edit, branch refresh, ref-mode
858+
change, cancel, directory change, or disposal advances that epoch, and a late
859+
completion cannot repopulate the view. `GithubBoardImporter` separately owns
860+
its fetch/commit generation,
815861
active cancellation token, and duplicate-commit lock, publishing typed
816862
fetch/recheck/upload progress and terminal results through callbacks/returns.
817863
Together these values render every lifecycle state above distinctly. At most

0 commit comments

Comments
 (0)