|
| 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 --> |
0 commit comments