phase: 135_git_source_branch
Build and Push Containers / build-and-push-app (push) Successful in 2m34s
Build and Push Containers / build-and-push-db (push) Successful in 14s

All verification complete. No edits were needed by this pass — the phase work and docs were already consistent and green.

**Phase 135 final verification pass — report**

- Verified all 5 tasks' work present & correct: migration 0024 (reversible), `GitSource.branch` + 4 schema shapes, `validate_branch_name` (full check order per design), `clone_or_pull(…, branch)` (HEAD guard first, `--branch` clone, fetch+ff merge), both call sites pass `row.branch`, UI (add-form field, editor field, row tag), `file://` URL transport.
- Completion criteria: (1) branch-mirrored through Sync + CLI — **pass** (E2E + integration); (2) `branch=None` byte-identical — **pass** (`test_branch_none_argv_is_byte_identical_pre_phase_sequence` green); (3) fixed 422s / local-row 422 / tri-state — **pass**; (4) mismatch fails loudly with remove-and-re-add — **pass** (E2E); (5) coverage 99% >90%, E2E isolated green — **pass**; (6) no regressions — **pass**.
- Tests: `uv run pytest --cov=app --cov-report=term-missing` → 3223 passed, app/ 99%; `uv run pytest tests/e2e/test_git_source_branch.py -v --no-cov` → 4 passed (16.4 s); `uv run ruff check .` clean; `uv run pyright` → 0 errors, 0 warnings.
- Documentation: README.md reviewed in full — phase-135 branch section accurate; the phase's README diff also correctly fixed a pre-existing stale line (shallow `--depth 1` clone vs full-history, phase 107). Routes, env defaults, feature lists verified against code. AGENTS.md — all 10 rules + quick-reference verified (referenced tests, PLAN.md, phases dirs, commands all accurate). **Verified consistent — no changes needed.**
- No live-infrastructure changes (dev DB already running and healthy; no live steps in this phase).
- Next pending phase: `.agents/phases/todo/136_force_push_reset`.
This commit is contained in:
2026-09-28 21:15:03 -04:00
parent fcac876d40
commit d3f8c7f2c8
50 changed files with 3955 additions and 140 deletions
+42 -17
View File
@@ -174,6 +174,7 @@ def _out_kwargs() -> dict:
"added_at": None,
"ignore_paths": [],
"include_hidden": False,
"branch": None,
}
@@ -186,6 +187,7 @@ def _row_kwargs() -> dict:
"added_at": None,
"ignore_paths": [],
"include_hidden": False,
"branch": None,
}
@@ -464,8 +466,19 @@ STYLES_CSS = FRONTEND / "assets" / "styles.css"
ADD_TOKEN_PLACEHOLDER = "ghp_… or another PAT"
EDIT_TOKEN_PLACEHOLDER = "leave blank to keep the current token"
ADD_BODY_EXPR = "(url, token) => ({ url, ...(token ? { token } : {}) })"
PATCH_BODY_EXPR = "JSON.stringify({ ignore_paths: lines, ...(token ? { token } : {}) })"
#: Phase 135 (task 04) widened the body to the branch pin — the token
#: key keeps its conditional shape (the contract under test) and the
#: branch key (git rows always — "" = the tri-state's clear leg) lands
#: first (test_git_source_branch_ui.py pins the branch contract).
ADD_BODY_EXPR = (
"(url, token, branch) => ({ url, ...(branch ? { branch } : {}), "
"...(token ? { token } : {}) })"
)
#: The phase-121 paragraph of the module docstring records the body AS
#: IT WAS in phase 121 (a historical record — the phase-135 paragraph
#: records the widened expression, pinned by test_git_source_branch_ui.py).
ADD_BODY_EXPR_PHASE_121 = "(url, token) => ({ url, ...(token ? { token } : {}) })"
PATCH_BODY_EXPR = "...(token ? { token } : {})"
def _read(path: Path) -> str:
@@ -526,20 +539,22 @@ def test_add_form_token_field_is_masked_and_optional() -> None:
def test_add_form_submit_omits_a_blank_token() -> None:
"""The submit body is `(url, token) => ({ url, ...(token ?
{ token } : {}) })` — a BLANK token omits the key (None = no
credential); wireAddForm reads the masked field and passes it to
the body callback, and 201 clears BOTH inputs (the credential is
stored — write-only)."""
"""The submit body is `(url, token, branch) => ({ url, ...(branch ?
{ branch } : {}), ...(token ? { token } : {}) })` — a BLANK token
omits the key (None = no credential; phase 135 added the branch
key, which also omits when blank); wireAddForm reads the masked
field and passes it to the body callback, and 201 clears ALL THREE
credential-free inputs (the credential is stored — write-only)."""
js = _js_text()
assert f"body: {ADD_BODY_EXPR}" in js, "the submit body omits the key when blank"
wire = _fn(js, "wireAddForm")
read_i = wire.find('tokenInput ? tokenInput.value.trim() : ""')
call_i = wire.find("opts.body(value, token)", read_i)
call_i = wire.find("opts.body(value, token, branch)", read_i)
assert -1 < read_i < call_i, "the token is read from the field and passed to the body"
clear_url_i = wire.find('input.value = ""')
clear_tok_i = wire.find("tokenInput.value = \"\"", clear_url_i)
assert -1 < clear_url_i < clear_tok_i, "201: both inputs clear"
clear_br_i = wire.find("branchInput.value = \"\"", clear_tok_i)
assert -1 < clear_url_i < clear_tok_i < clear_br_i, "201: all three inputs clear"
def test_editor_token_field_is_masked_and_blanks_to_keep() -> None:
@@ -570,15 +585,23 @@ def test_editor_token_field_is_masked_and_blanks_to_keep() -> None:
def test_editor_patch_omits_a_blank_token() -> None:
"""saveIgnorePaths: the token is read AFTER the line parse and the
PATCH body is `{ ignore_paths: lines, ...(token ? { token } : {}) }`
— a BLANK token omits the key (the task-01 tri-state: absent = no
change, the row's stored credential is kept)."""
PATCH body keeps the token as the CONDITIONAL key
(`...(token ? { token } : {})` — a BLANK token omits the key, the
task-01 tri-state: absent = no change, the row's stored credential
is kept). Phase 135 (task 04) widens the body with the branch key
(`...(isGitRow ? { branch } : {})` — ALWAYS for git rows: an empty
save is the deliberate clear leg, unlike the token's blank = keep)
landing before the token key — the token's semantics are untouched
(the branch contract is pinned by test_git_source_branch_ui.py)."""
js = _js_text()
save = _fn(js, "saveIgnorePaths")
drop_i = save.find(".filter(Boolean)")
read_i = save.find('ignoreTokenInput ? ignoreTokenInput.value.trim() : ""', drop_i)
body_i = save.find(PATCH_BODY_EXPR, read_i)
assert -1 < drop_i < read_i < body_i, ("blank token → key omitted (tri-state no-change)")
branch_i = save.find("...(isGitRow ? { branch } : {})", read_i)
body_i = save.find(PATCH_BODY_EXPR, branch_i)
assert -1 < drop_i < read_i < branch_i < body_i, (
"blank token → key omitted (tri-state no-change; the branch key lands first)"
)
def test_display_sites_render_the_server_bare_url_only() -> None:
@@ -609,7 +632,7 @@ def test_module_docstring_carries_the_phase_121_token_contract() -> None:
"#git-source-token",
"type=password",
"optional — private repos",
ADD_BODY_EXPR,
ADD_BODY_EXPR_PHASE_121,
"leave blank to keep the current token",
"sanitize_url",
):
@@ -625,7 +648,9 @@ def test_token_field_css_rules_are_house_styled() -> None:
width at the 44px floor; the mobile media query groups it in the
same min-width:0 rule as the URL input."""
css = _read(STYLES_CSS)
grouped = css.find("#git-source-url,\n#git-source-token {")
# Phase 135 (task 04): the branch input joined the group — the
# token input still shares the URL input's box.
grouped = css.find("#git-source-url,\n#git-source-branch,\n#git-source-token {")
assert grouped != -1, "#git-source-token shares #git-source-url's box"
rule = css[grouped : css.find("}", grouped)]
assert "min-height: 44px" in rule and "var(--mono)" in rule
@@ -637,5 +662,5 @@ def test_token_field_css_rules_are_house_styled() -> None:
assert tok != -1, "the editor's token field rule"
tok_rule = css[tok : css.find("}", tok)]
assert "width: 100%" in tok_rule and "min-height: 44px" in tok_rule
mobile = css.find("#git-source-url,\n #git-source-token,")
mobile = css.find("#git-source-url,\n #git-source-branch,\n #git-source-token,")
assert mobile != -1, "the mobile squeeze groups the token input"