Auto-apply repository topics to GitHub-synced projects - #340
demo-zexuan wants to merge 9 commits into
Conversation
Paper projects pile up as private repositories whose names all look alike, and GitHub search cannot tell them apart. The paper id is already stored locally, so publish it as repository topics and make projects findable by filtered search. - Add openresearch to every synced project, plus arxiv-xxxx (version suffix dropped so v1 and v2 share a topic) and paper-repro when the project comes from a paper. - Let people add their own topics on top, and turn the automatic ones off, per project and in the new-project defaults. - Edit both in project settings, not only at creation time. - Merge with the repository's existing topics rather than replacing them: a repository shared by several projects, or one whose owner added topics by hand, keeps them. Never publish an empty set. - Backfill projects that already sync to GitHub, once, via the v2 schema migration. Publishing topics decorates a push that already succeeded, so a GitHub failure there warns instead of failing project creation. Refs alphaXiv#339
There was a problem hiding this comment.
🟡 Changes recommended
There are a few confirmed functional issues (topic editor state not resetting on project switch, defaults prompt overwriting auto-topics default, merge semantics risking topic loss on GET failure, and topic edits not being published immediately) that should be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR adds first-class support for publishing GitHub repository topics for GitHub-synced projects, including configurable defaults, per-project overrides, and a one-time backfill for already-synced projects via a schema migration. It extends both the UI and the local HTTP API / SQLite store so topics can be toggled and edited after project creation.
Changes:
- Add repo-topic derivation/sanitization + best-effort publication (merge/union semantics) during GitHub sync/push flows.
- Add per-project topic settings (auto-topics toggle + custom topics) and defaults for new projects, including a migration backfill.
- Update UI flows (new project form + settings) and i18n strings to expose the new controls.
File summaries
| File | Description |
|---|---|
| ui/src/components/SettingsPage.tsx | Adds defaults toggle + per-project topic editor UI and wires it to the API. |
| ui/src/components/NewProjectForm.tsx | Adds project-creation fields for auto-topics + custom topics. |
| ui/src/api.ts | Extends API types and PATCH payload to include topic settings; extends project defaults payload. |
| ui/messages/en.json | Adds strings for topic toggles/editor. |
| ui/messages/zh-CN.json | Adds strings for topic toggles/editor. |
| ui/messages/fa.json | Adds strings for topic toggles/editor. |
| ui/dist/index.html | Rebuilds UI bundle reference after UI changes. |
| src/local/github.rs | Implements topic normalization, topic derivation, and GitHub topic GET/PUT merge logic via gh api. |
| src/commands/up.rs | Extends create/update project API and triggers topic sync after GitHub push/sync operations. |
| src/store.rs | Adds SQLite columns for topic settings and schema migration/backfill for already-synced projects. |
| src/local/model.rs | Adds new fields to LocalProject plus defaulting and row decoding for the new columns. |
| src/local/projects.rs | Stores topic settings on project creation. |
| src/telemetry.rs | Adds persisted default for auto-topics-for-new-projects. |
| src/config.rs | Exposes getters/setters for the new auto-topics default. |
| src/local/starter.rs | Updates test fixtures to use Default::default() for new columns. |
| src/local/resolve.rs | Updates test fixtures to use Default::default() for new columns. |
| src/local/opencode.rs | Updates test fixtures to use Default::default() for new columns. |
| src/local/experiments.rs | Updates test fixtures to use Default::default() for new columns. |
| src/local/demo.rs | Updates demo seed/test fixtures to use Default::default() for new columns. |
| src/local/chat/mod.rs | Updates test fixtures to use Default::default() for new columns. |
Review details
- Files reviewed: 19/21 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Abort topic publication when the repository's current topics cannot be read. Treating an unreadable list as empty let a transient GET failure turn the merge into a replace, deleting the topics it exists to keep. - Publish topics right after a settings edit instead of waiting for the next push, so the editor has a visible effect. - Drop the auto-topics flag from the publish guard. It decided whether anything was worth publishing, which meant custom topics never reached the repository while automatic tagging was off. - Re-seed the topics editor when the project changes; it was seeded once, so switching projects could show and then save the previous project's topics onto the new one. - Keep the auto-topics default when answering the one-time sync prompt, which only asks about syncing.
The publish guard used it to decide whether to sync; that guard now checks whether the computed topic set is empty, so nothing calls it.
There was a problem hiding this comment.
🟡 Changes recommended
The UI changes introduce a couple of concrete accessibility/UX correctness issues (control labeling and dirty-state normalization) that should be fixed before approval.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 19/21 changed files
- Comments generated: 4
- Review effort level: Lite
- Wrap the new-project topics checkbox in a label so the visible text names the control and the whole row is clickable. - Give the extra-topics field an accessible name; it relied on its placeholder, which disappears once the user types. - Point the per-project topics switch at the repository-topics label instead of the new-project defaults one, which described the wrong thing. - Compare the parsed topic sets when deciding dirty state. Comparing raw strings treated "a,b" and "a, b" as an edit and sent a pointless PATCH.
There was a problem hiding this comment.
🟡 Changes recommended
There are two verified correctness/operational issues in the new defaults/topic publication flows that can cause unintended settings overwrites and unnecessary repeated GitHub API writes.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 19/22 changed files
- Comments generated: 2
- Review effort level: Lite
- Skip the topic PUT by comparing the stored and merged sets rather than their order. GitHub need not echo our PUT order back, so the previous check would have re-written topics on every push and burned API calls. - Drop the cached prompt-seen flag from the auto-topics toggle. That handler only owns the auto-topics default, and re-sending a stale flag could undo a one-time prompt another flow had just answered.
There was a problem hiding this comment.
🟡 Changes recommended
The GitHub-sync default prompt flow updates server settings but does not update/invalidate the cached project-defaults query, which can leave the UI stale and cause later toggles to accidentally overwrite the sync default.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 19/22 changed files
- Comments generated: 1
- Review effort level: Lite
The one-time prompt updated the server but left getProjectDefaultsQuery holding the pre-answer values. The settings page and the new-project form both read that cache, so a later toggle would send the stale value back and undo the answer. Write the response into the shared cache instead.
There was a problem hiding this comment.
🟡 Changes recommended
The project-defaults endpoint currently overwrites the independent auto-topics default even when the client doesn’t send it, which can unintentionally clobber user preferences (forward/backward compatibility issue).
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 19/22 changed files
- Comments generated: 2
- Review effort level: Lite
Sending an absent auto-topics field is not neutral: the handler derived a value from the sync flag and stored it, so a client that only toggled syncing froze the auto-topics default at that answer for good. Persist the field only when the client sends it and let the existing fallback track the sync default for anyone who never chose one. The sync toggle in the defaults tab now omits the field for the same reason, and the stale-cache comment on the auto-topics toggle says what the handler actually sends.
There was a problem hiding this comment.
🟡 Changes recommended
There are at least two concrete behavioral bugs (old-style arXiv topic normalization dropping archive prefixes, and project creation persisting hidden topic fields when GitHub sync is off) that should be corrected before approval.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 19/22 changed files
- Comments generated: 2
- Review effort level: Lite
- Preserve the archive prefix of old-style ids: hep-th/9901001 now yields arxiv-hep-th-9901001 instead of arxiv-9901001, which several archives could share. - Make the parser total. It previously sliced at fixed byte offsets and split on the last slash, so a multi-byte or odd id could panic or index past the end. It now walks character boundaries and validates the id shape, returning no topic when the id cannot be understood, because publishing topics must never take a successful push down with it. - Understand URL forms uniformly: query strings, fragments, /pdf/ links and a trailing .pdf all resolve to the same topic, and the version suffix is dropped whether or not the extension is still attached. - Stop sending topic fields when GitHub sync is off in the new-project form. They are hidden in that state, so the request was persisting values the user could not see.
There was a problem hiding this comment.
🟡 Changes recommended
There are a few concrete issues (accessibility labeling ambiguity in the topic editor UI and a redundant settings.json read) that should be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
ui/src/components/SettingsPage.tsx:3197
- This input’s aria-label is the generic "Repository topics" (also used by the toggle above). For accessibility, the field should be labeled as the extra/comma-separated topics input so it’s distinguishable from the toggle.
aria-label={m.settings_page_repository_topics()}
- Files reviewed: 19/22 changed files
- Comments generated: 3
- Review effort level: Lite
- Derive the auto-topics fallback from the same settings load instead of calling back into github_for_new_projects(). The second read was extra I/O and could see a different file if settings.json was rewritten in between. - Name the new-project topics input after the field itself rather than the generic "Repository topics" label. - Give the per-project topics switch its own label, so it is no longer announced with the same name as the text field below it.
|
I've addressed all 18 findings from the review so far. One correction for the record, though: every one of them landed in code this PR introduces (the topic helpers in Where I do agree is on scope. The first three rounds found real correctness problems, and I fixed all of them — worth calling out that the strictest one was mine to own: treating an unreadable topics list as empty turned the merge into a replace, and the publish guard gated on the auto-topics flag, which meant custom topics never reached the repository while automatic tagging was off. Since then the findings have shifted to accessibility and code hygiene in the same new code (label wording, duplicate If anything else comes up that is a correctness problem rather than polish, I'll of course fix it. Otherwise I'd rather let the maintainers' review be the judge of the remaining nits, instead of expanding this PR further. One thing I can't do myself: CI has been queued on every push and has not run once ( |
Refs #339
Paper projects pile up as private repositories whose names all look alike, and GitHub search cannot tell them apart. The paper id is already stored locally, so publish it as repository topics and make projects findable with filtered search:
What it does
openresearchto every synced project, plusarxiv-xxxxandpaper-reprowhen the project comes from a paper. The arxiv id drops its version suffix, so v1 and v2 of the same paper share one topic instead of splitting search results.Design decisions worth reviewing
Merge instead of replace.
PUT /repos/{owner}/{repo}/topicsreplaces the whole set, so publishing the computed set directly would wipe out topics added by hand on GitHub, and two projects pointing at the same repository would overwrite each other.set_repo_topicsreads the repository's current topics and takes the union, skips the write when nothing changed, and never publishes an empty set. The trade-off: orx cannot remove a topic, so dropping one from a project leaves it on the repo until it is removed on GitHub.Topic publication is best-effort. Topics are metadata on a push that already succeeded, so a GitHub failure there logs a warning rather than failing project creation or an experiment publish.
Points for the reviewer
gh apitopic calls are covered by unit tests at the pure-function and SQL level only — I have not exercised them against a live repository, so the PUT/GET round-trip is the main thing to verify.Verification
cargo fmt --all --checkandcargo clippy --all-targets -- -D warningspass.cargo testpasses exceptcommands::up::tests::pty_relays_input_output_resize_exit_and_cancel, which fails in my environment because the sandbox deniesopenpty. UItsc --noEmit, the i18n and style-token linters pass, andui/distis rebuilt.Test Image