From 7bd9f49ed7cb8482226d70ffaaa3d83fde194710 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 6 Jun 2026 18:27:47 -0500 Subject: fix(scripts): make Triage the intake column and the default new-issue state Two problems showed up dogfooding the seeded board. New issues landed in Backlog, because Linear's teamCreate sets the team's default new-issue state to its own Backlog default and the seed never changed it. And the intake column read "Triage Queue" when "Triage" is the name we want. The script now points the team's defaultIssueStateId at the intake column after the columns reconcile, so a fresh issue opens in Triage instead of Backlog. It skips the update when the default is already correct, so a re-run stays a no-op. The intake column is renamed Triage throughout: the target state, the Pearl Inbox view filter, and INTAKE_STATE. find_team reads defaultIssueState so the idempotence check has something to compare. Both mutations were schema-introspected first: TeamUpdateInput.defaultIssueStateId and CustomViewUpdateInput.filterData. The live board was fixed the same way (rename, default-state, Inbox filter) and the dry-run against it comes back all-update, no-create. 13 tests cover the rename, the default-state set on a fresh seed, and the skip when it's already Triage. --- scripts/tests/test_seed_pearl_workspace.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'scripts/tests') diff --git a/scripts/tests/test_seed_pearl_workspace.py b/scripts/tests/test_seed_pearl_workspace.py index ea22230..fdf967b 100644 --- a/scripts/tests/test_seed_pearl_workspace.py +++ b/scripts/tests/test_seed_pearl_workspace.py @@ -9,7 +9,7 @@ The state reconcile is shaped by what Linear actually allows, verified live: teamCreate seeds its own default columns, a team must keep an unstarted state, and the Duplicate state is reserved. So the script never archives -- it renames Linear's defaults into the six Agile columns (repurposing the unstarted Todo as -Triage Queue), creates only the genuinely-new Icebox, and leaves Duplicate +Triage), creates only the genuinely-new Icebox, and leaves Duplicate alone. Positions are set with distinct nonzero values, since Linear ignores a 0.0 position and appends new states at a high one. """ @@ -28,13 +28,13 @@ import seed_pearl_workspace as seed def test_target_states_are_the_six_agile_columns_in_order(): assert [s["name"] for s in seed.TARGET_STATES] == [ - "Icebox", "Triage Queue", "Backlog", "In Progress", "Done", "Canceled"] + "Icebox", "Triage", "Backlog", "In Progress", "Done", "Canceled"] def test_target_state_types_and_distinct_nonzero_positions(): by_name = {s["name"]: s for s in seed.TARGET_STATES} assert by_name["Icebox"]["type"] == "backlog" - assert by_name["Triage Queue"]["type"] == "unstarted" # repurposed from Todo + assert by_name["Triage"]["type"] == "unstarted" # repurposed from Todo assert by_name["Backlog"]["type"] == "backlog" assert by_name["In Progress"]["type"] == "started" assert by_name["Done"]["type"] == "completed" @@ -46,7 +46,7 @@ def test_target_state_types_and_distinct_nonzero_positions(): def test_triage_queue_sources_the_default_todo(): - tq = next(s for s in seed.TARGET_STATES if s["name"] == "Triage Queue") + tq = next(s for s in seed.TARGET_STATES if s["name"] == "Triage") assert "Todo" in tq["sources"] @@ -67,7 +67,7 @@ def test_open_view_excludes_completed_and_cancelled(): def test_icebox_and_inbox_views_filter_to_their_column(): views = {v["name"]: v for v in seed.desired_views("p")} assert views["Pearl Icebox"]["filterData"]["state"]["name"]["eq"] == "Icebox" - assert views["Pearl Inbox"]["filterData"]["state"]["name"]["eq"] == "Triage Queue" + assert views["Pearl Inbox"]["filterData"]["state"]["name"]["eq"] == "Triage" # --- pure: the state reconcile planner ---------------------------------------- @@ -89,9 +89,9 @@ def _plan_by_target(existing): def test_reconcile_against_linear_defaults_renames_and_creates(): plan = _plan_by_target(LINEAR_DEFAULTS) - # Icebox is genuinely new; the rest map onto a default (Triage Queue <- Todo). + # Icebox is genuinely new; the rest map onto a default (Triage <- Todo). assert plan["Icebox"] is None - assert plan["Triage Queue"] == "d2" # the unstarted Todo + assert plan["Triage"] == "d2" # the unstarted Todo assert plan["Backlog"] == "d1" assert plan["In Progress"] == "d3" assert plan["Done"] == "d4" @@ -100,7 +100,7 @@ def test_reconcile_against_linear_defaults_renames_and_creates(): def test_reconcile_is_idempotent_against_already_named_columns(): existing = [{"id": f"s{i}", "name": n, "type": "x"} for i, n in enumerate( - ["Icebox", "Triage Queue", "Backlog", "In Progress", "Done", "Canceled"])] + ["Icebox", "Triage", "Backlog", "In Progress", "Done", "Canceled"])] plan = _plan_by_target(existing) assert all(mid is not None for mid in plan.values()) # everything matches, nothing created @@ -148,6 +148,7 @@ def test_seed_on_empty_workspace_reconciles_then_builds(): "TeamStates": {"data": {"team": {"states": {"nodes": LINEAR_DEFAULTS}}}}, "StateCreate": _ok("workflowStateCreate", {"workflowState": {"id": "new1"}}), "StateUpdate": _ok("workflowStateUpdate", {"workflowState": {"id": "u"}}), + "TeamUpdate": _ok("teamUpdate", {}), "Projects": {"data": {"projects": {"nodes": []}}}, "ProjectCreate": _ok("projectCreate", {"project": {"id": "p1", "name": "Pearl"}}), "CustomViews": {"data": {"customViews": {"nodes": []}}}, @@ -158,23 +159,31 @@ def test_seed_on_empty_workspace_reconciles_then_builds(): assert ops.count("TeamCreate") == 1 assert ops.count("StateCreate") == 1 # only Icebox is new assert ops.count("StateUpdate") == 6 # six columns positioned/renamed + assert ops.count("TeamUpdate") == 1 # default new-issue state -> Triage assert ops.count("ProjectCreate") == 1 assert ops.count("CustomViewCreate") == 3 assert summary["team"] == "t1" assert summary["project"] == "p1" assert summary["leftover_states"] == ["Duplicate"] + assert summary["default_issue_state"] == "Triage" + # the default points at Triage (which reconciled from the unstarted Todo, d2) + team_update = next(v for op, v in fake.calls if op == "TeamUpdate") + assert team_update["input"]["defaultIssueStateId"] == "d2" assert "workflowStateArchive" not in ops # never archives def test_seed_is_idempotent_against_a_seeded_workspace(): seeded_states = [{"id": f"s{i}", "name": n, "type": "x"} for i, n in enumerate( - ["Icebox", "Triage Queue", "Backlog", "In Progress", "Done", "Canceled", "Duplicate"])] + ["Icebox", "Triage", "Backlog", "In Progress", "Done", "Canceled", "Duplicate"])] fake = FakeTransport({ + # default already points at Triage (s1), so no team update fires either "Teams": {"data": {"teams": {"nodes": [ {"id": "t1", "key": "PEARL", "name": "Pearl", + "defaultIssueState": {"id": "s1"}, "states": {"nodes": seeded_states}}]}}}, "TeamStates": {"data": {"team": {"states": {"nodes": seeded_states}}}}, "StateUpdate": _ok("workflowStateUpdate", {"workflowState": {"id": "u"}}), + "TeamUpdate": _ok("teamUpdate", {}), "Projects": {"data": {"projects": {"nodes": [{"id": "p1", "name": "Pearl"}]}}}, "CustomViews": {"data": {"customViews": {"nodes": [ {"id": "v1", "name": "Pearl Open Issues"}, @@ -185,6 +194,7 @@ def test_seed_is_idempotent_against_a_seeded_workspace(): ops = fake.ops() for create_op in ("TeamCreate", "StateCreate", "ProjectCreate", "CustomViewCreate"): assert create_op not in ops # nothing re-created + assert "TeamUpdate" not in ops # default already correct def test_client_raises_on_graphql_errors(): -- cgit v1.2.3