fix(board): page the board list #147
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/YT-91-board-list-pagination"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
yt board listcalledclient.get_json("/api/agiles?fields=id,name")directly instead of going throughapi::list_boards, so the request carried no$top/$skipand never asked for a second page: the command returned whatever the server's default page held and silently dropped the rest. The MCP already read throughapi::list_boards, so the two front ends could report different board lists for the same instance. The command now callslist_boards; the--jsonoutput and the table rendering are unchanged.The invariant is "every collection read pages until a short page (never silently truncates)", and
fetch_all_pagesis its single implementation. Sweeping everyget_jsoncall whose path is a collection endpoint acrosscrates/yt-cliandcrates/youtrack-clientfound five more violations, all fixed here.yt-cli/src/commands/board.rs:44/api/agilesapi::list_boardsapi.rs:41fetch_all_pagesapi.rs:113get_board/api/agiles/<id>projects/sprintscollections already read through their own paged subresourcesapi.rs:207fetch_all_sprints/api/agiles/<id>/sprints$top/$skiploopapi.rs:235list_issue_activities/api/issues/<id>/activities$top/$skiploop, bounded by the caller'slimitapi.rs:283list_issue_sprints/api/issues/<id>/sprintsapi.rs:297inspect_issue(issue)/api/issues/<id>api.rs:330inspect_issue(links)/api/issues/<id>/linksapi.rs:366fetch_issues(issues)/api/issuesfetch_all_pagesapi.rs:366fetch_issues(sprints)/api/issues/<id>/sprintslist_issue_sprintsapi.rs:647discover_issue_field_type/api/admin/projects/<id>/customFieldsproject_custom_fields_by_idalready pages the same pathapi.rs:991fetch_issues_page(issues)/api/issues$top/$skipare the caller's (yt list --top/--skip), andfetch_all_issuesis the loop over itapi.rs:991fetch_issues_page(sprints)/api/issues/<id>/sprintslist_issue_sprintsapi.rs:1053get_user_me/api/users/meapi.rs:1062list_users/api/users$topis always sent and is the CLI's--top(default 50)api.rs:1076get_user/api/users/<login>api.rs:1261get_server_config/api/configapi.rs:1268list_project_states/api/admin/projects/<id>/customFieldsdiscover_issue_field_typeapi.rs:1309get_issue_custom_fields/api/issues/<id>api.rs:1373list_project_vcs_processors/api/admin/projects/<id>processorsis a nested attribute of that entity, not a collection endpoint, and is bounded by the project's configured VCS integrationsapi.rs:1728project_team_by_id/api/admin/projects/<id>/teamapi.rs:3065list_articles/api/articles--top, and theNonearm callsfetch_all_pagesapi.rs:3074get_article/api/articles/<id>api.rs:3215build_timeline/api/issues/<id>api.rs:3369fetch_link_slots/api/issues/<id>/linksinspect_issuelinks readapi.rs:3476fetch_issue_identity/api/issues/<id>crates/youtrack-mcphas noget_jsoncall of its own; it reads throughyoutrack_client::apithroughout.Regression cover:
commands::board::tests::list_pages_until_a_short_pagemounts a full page plus a short page on/api/agilesand assertsboard listrequests both and surfaces the union (101 boards), andapi::testsgainslist_issue_sprints_paginates,fetch_link_slots_paginates, andlist_project_states_paginates.crates/yt-cli/tests/no_raw_collection_reads.rsfails the build if any CLI source callsget_jsondirectly again, so the removal is enforced rather than asserted.#YT-91
`yt board list` called `client.get_json("/api/agiles?fields=id,name")` directly instead of going through `api::list_boards`, so the request carried no `$top` / `$skip` and never asked for a second page: the command returned whatever the server's default page held and silently dropped the rest. The MCP already read through `api::list_boards`, so the two front ends could report different board lists for the same instance. The command now calls `list_boards`; the `--json` output and the table rendering are unchanged. The invariant is "every collection read pages until a short page (never silently truncates)", and `fetch_all_pages` is its single implementation. Sweeping every `get_json` call whose path is a collection endpoint across `crates/yt-cli` and `crates/youtrack-client` found five more violations, all fixed here. | Site | Endpoint | Class | Reason | | --- | --- | --- | --- | | `yt-cli/src/commands/board.rs:44` | `/api/agiles` | violating -> fixed | now `api::list_boards` | | `api.rs:41` `fetch_all_pages` | any | compliant | the pager itself | | `api.rs:113` `get_board` | `/api/agiles/<id>` | N/A | single-entity read; its `projects` / `sprints` collections already read through their own paged subresources | | `api.rs:207` `fetch_all_sprints` | `/api/agiles/<id>/sprints` | compliant | own `$top` / `$skip` loop | | `api.rs:235` `list_issue_activities` | `/api/issues/<id>/activities` | compliant | own `$top` / `$skip` loop, bounded by the caller's `limit` | | `api.rs:283` `list_issue_sprints` | `/api/issues/<id>/sprints` | violating -> fixed | an issue's sprint membership is admin-shaped, not structurally bounded | | `api.rs:297` `inspect_issue` (issue) | `/api/issues/<id>` | N/A | single-entity read; its comments and work items already page | | `api.rs:330` `inspect_issue` (links) | `/api/issues/<id>/links` | violating -> fixed | one slot per link type and direction, and link types are admin-created | | `api.rs:366` `fetch_issues` (issues) | `/api/issues` | compliant | `fetch_all_pages` | | `api.rs:366` `fetch_issues` (sprints) | `/api/issues/<id>/sprints` | violating -> fixed | same shape as `list_issue_sprints` | | `api.rs:647` `discover_issue_field_type` | `/api/admin/projects/<id>/customFields` | violating -> fixed | a project's field count is admin-shaped; the canonical reader `project_custom_fields_by_id` already pages the same path | | `api.rs:991` `fetch_issues_page` (issues) | `/api/issues` | N/A | deliberate single page: `$top` / `$skip` are the caller's (`yt list --top` / `--skip`), and `fetch_all_issues` is the loop over it | | `api.rs:991` `fetch_issues_page` (sprints) | `/api/issues/<id>/sprints` | violating -> fixed | same shape as `list_issue_sprints` | | `api.rs:1053` `get_user_me` | `/api/users/me` | N/A | single-entity read | | `api.rs:1062` `list_users` | `/api/users` | N/A | deliberate bound: `$top` is always sent and is the CLI's `--top` (default 50) | | `api.rs:1076` `get_user` | `/api/users/<login>` | N/A | single-entity read | | `api.rs:1261` `get_server_config` | `/api/config` | N/A | single-entity read | | `api.rs:1268` `list_project_states` | `/api/admin/projects/<id>/customFields` | violating -> fixed | same shape as `discover_issue_field_type` | | `api.rs:1309` `get_issue_custom_fields` | `/api/issues/<id>` | N/A | single-entity read | | `api.rs:1373` `list_project_vcs_processors` | `/api/admin/projects/<id>` | N/A | single-entity read; `processors` is a nested attribute of that entity, not a collection endpoint, and is bounded by the project's configured VCS integrations | | `api.rs:1728` `project_team_by_id` | `/api/admin/projects/<id>/team` | N/A | single-entity read; its three collections read through their own paged subresources (YT-83) | | `api.rs:3065` `list_articles` | `/api/articles` | N/A | deliberate bound: this arm runs only when the caller passed `--top`, and the `None` arm calls `fetch_all_pages` | | `api.rs:3074` `get_article` | `/api/articles/<id>` | N/A | single-entity read | | `api.rs:3215` `build_timeline` | `/api/issues/<id>` | N/A | single-entity read, once per already-resolved id | | `api.rs:3369` `fetch_link_slots` | `/api/issues/<id>/links` | violating -> fixed | same shape as the `inspect_issue` links read | | `api.rs:3476` `fetch_issue_identity` | `/api/issues/<id>` | N/A | single-entity read | `crates/youtrack-mcp` has no `get_json` call of its own; it reads through `youtrack_client::api` throughout. Regression cover: `commands::board::tests::list_pages_until_a_short_page` mounts a full page plus a short page on `/api/agiles` and asserts `board list` requests both and surfaces the union (101 boards), and `api::tests` gains `list_issue_sprints_paginates`, `fetch_link_slots_paginates`, and `list_project_states_paginates`. `crates/yt-cli/tests/no_raw_collection_reads.rs` fails the build if any CLI source calls `get_json` directly again, so the removal is enforced rather than asserted. #YT-91