Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
#### [unreleased]
* add GitHub OAuth-powered autocomplete to the Install Plugin/Theme screen — repo URI field shows a live-filtered dropdown of all repositories (personal + organization) accessible via the connected OAuth account, with debounce, spinner, keyboard navigation (↑ ↓ Enter Esc), and ARIA attributes
* add branch autocomplete to the Repository Branch field — once a connected-account repo URI is entered, typing in the branch field filters available branches fetched from the GitHub API
* add branch autofill — when a connected-account repo is selected and its default branch is not `master`, the Repository Branch field is populated automatically
* add field-hiding — when the entered URI belongs to the connected GitHub account (personal or org), the Remote Repository Host selector and GitHub Access Token field are hidden as they are not needed for OAuth-authenticated installs
* add `Install::ajax_github_repos()` AJAX handler (`gu_github_repos`) — returns paginated, cached repo list for the connected account, searched by query string
* add `Install::ajax_github_branches()` AJAX handler (`gu_github_branches`) — returns branch names for a given `owner/repo`, cached 5 minutes
* add `Install::ajax_github_repo_info()` AJAX handler (`gu_github_repo_info`) — returns `default_branch` and owner for a given repo, cached 5 minutes
* add `Install::fetch_all_github_repos()` — paginates `/user/repos` and all organization repos via `/user/orgs` + `/orgs/{org}/repos`, deduplicates by `full_name`, capped at 1 000 items
* add `Install::github_paginate()` — shared paginator for GitHub API endpoints
* add `Install::get_github_username()` — fetches and caches the authenticated user's login for 1 hour
* add `Install::get_github_org_logins()` — fetches and caches the authenticated user's organization slugs for 1 hour, used by JS to recognize org repos as connected-account repos
* add `Install::register_ajax_handlers()` — hooked from `Settings::load_hooks()` so AJAX actions are registered on all admin requests including `admin-ajax.php`
* fix `Install` AJAX handlers were unreachable during `wp_doing_ajax()` because `Settings::page_init()` (which called `Install::run()`) was gated behind `! wp_doing_ajax()`; handlers are now registered in `Settings::load_hooks()` instead
* fix `Rest_Update::update_plugin()` inverted activation check — `activate_plugin()` returns `null` on success and `WP_Error` on failure; old code `if ( ! $activate )` silently swallowed failures; now reports error message from `WP_Error`
* fix `API::api()` missing `WP_Error` check after OAuth retry — retry response now validated like the initial request
* fix `API::get_release_asset_redirect()` AWS cache age calculation to use `$this->hours` instead of hardcoded `-12 hours`
Expand Down
32 changes: 32 additions & 0 deletions docs/claude-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,38 @@ Plugin/theme repo objects (`$this->type`) are `stdClass` instances populated wit

All plugin options are stored in a single site option `git_updater` (an array). `GU_Upgrade` handles migration from legacy `github_updater` option names. Settings UI is in `Settings.php`; per-repo authentication fields are added via `gu_add_settings` and `gu_add_repo_setting_field` filters implemented in each API class.

### Install screen GitHub OAuth autocomplete

`Install.php` adds three `wp_ajax_*` AJAX handlers that power the autocomplete UX on the Install Plugin / Install Theme tab when a GitHub OAuth token is active.

**AJAX registration** — `Install::register_ajax_handlers()` is called from `Settings::load_hooks()` (not from `Install::run()`) so the handlers are available on all admin requests, including `admin-ajax.php`. `Install::run()` is gated behind `! wp_doing_ajax()` in `Settings::page_init()`, so any hook registration inside `run()` would be invisible to AJAX requests.

**`gu_github_repos`** — Accepts a `q` query param. Calls `fetch_all_github_repos()` which paginates `/user/repos` (type=all) and then paginates `/orgs/{org}/repos` for every org returned by `/user/orgs`. Results are deduplicated by `full_name` and cached in a site transient keyed by `md5($token)` for 5 minutes. The handler filters the cached list by `q` and returns up to 20 matches.

**`gu_github_branches`** — Accepts a `repo` param (`owner/repo`). Paginates `/repos/{owner}/{repo}/branches` and caches the branch name list for 5 minutes.

**`gu_github_repo_info`** — Accepts a `repo` param. Fetches `/repos/{owner}/{repo}` and returns `default_branch`, `private`, and `owner` login. Cached 5 minutes.

**Script data** — `load_js()` localises `guInstallData` onto `gu-install` with:
- `ajaxurl` — `admin-ajax.php` URL
- `nonce` — `gu_github_install_autocomplete` nonce
- `github_oauth` — `'1'` when a GitHub OAuth token is stored
- `github_username` — authenticated user's login (from `/user`, cached 1 hour via `get_github_username()`)
- `github_orgs` — lowercase array of org slugs the user belongs to (from `/user/orgs`, cached 1 hour via `get_github_org_logins()`), used to recognise org repos as "connected account" repos

**JS behaviour** (`js/gu-install-vanilla.js`) — The autocomplete runs only when `github_oauth === '1'`. Both the URI and Branch fields get independent dropdowns with:
- Debounced input (250 ms) with an immediate CSS spinner while waiting
- Keyboard navigation: ↑/↓ moves highlight, Enter selects, Escape closes
- ARIA: `role="combobox"` on inputs, `role="listbox"` on lists, `role="option"` on items, `aria-expanded`, `aria-activedescendant`
- `li._guRepo` / `li._guBranch` properties store the data object for keyboard selection without re-parsing `dataset`

When a URI from a connected account is entered or selected, `applyConnectedRepoState()`:
1. Sets the host dropdown to `github` and dispatches a `change` event (to trigger existing show/hide logic for other API token fields)
2. Calls `hideHostAndTokenFields()` **after** the dispatch — the change handler would otherwise re-show the `github_setting` rows
3. Fetches and autofills the default branch if the field is empty or set to `master` and the actual default differs

The "connected account" check compares the repo owner (extracted from full URL or `owner/repo` slug) against `github_username` and all entries in `github_orgs`.

### Coding standards

PHPCS uses the `WordPress` ruleset with several exclusions defined in `phpcs.xml`. Notable: short array syntax (`[]`) is enforced, file naming and variable naming WordPress conventions are relaxed, and some Squiz control structure rules are disabled.
Loading