Repository navigation
SkillIssues v1.0.0 — Production Deployment & Unified Client Architecture
SkillIssues — an open-source issue discovery platform and desktop contribution engine. Browse real GitHub issues by difficulty, start working on one from the desktop app with one click (fork, clone, branch — all automatic), and track your contribution through to a merged PR.
Download the desktop client
- Windows:
SkillIssues.Setup.1.0.0.exe - macOS (Apple Silicon):
SkillIssues-1.0.0-arm64.dmg - Linux:
SkillIssues-1.0.0.AppImage
🚀 Production Deployment & Unified Client Architecture
1.0.0 is the release where SkillIssues actually went live, and where the web and desktop
clients stopped drifting apart. The app now runs on a real VPS behind CI-gated deploys, and
both clients share one typed API layer and one set of UI components instead of reimplementing
the product twice.
Added
Production deployment (first real deploy of this project)
- CI/CD pipeline:
.github/workflows/ci.ymlgates every push — backend (vet,gofmt,
build, test), web/desktop (typecheck and a real production build, not just a type check),
and abuildxbuild of all five Docker targets plusdocker compose configon both compose
files..github/workflows/deploy.ymlbuilds and pushes
ghcr.io/dizziedbliss/skillissues-{api,ingestd,migrate,web}on every push tomain, gated on
CI passing, then deploys to the VPS over SSH. Rollback is aworkflow_dispatchwith an older
immutablesha-tag. - One root
Dockerfilewith named targets (api,ingestd,migrate,web,
repoimport) built from the repo root, replacing four scattered Dockerfiles, so theweb
target can actually see the sharedpackages/*it imports. New root.dockerignorekeeps
the build context to tens of MB instead of shipping the multi-GB dataset. compose.prod.yaml: a from-scratch production compose file — Caddy as the only
internet-facing service (automatic TLS, same-origin/api/*routing so there's no CORS and
no cross-site cookie problem), Postgres/Redis sized for the 4GB/2vCPU VPS, explicit memory and
CPU limits and log rotation on every service,DB_MAX_CONNSmade configurable.- Disk guardrails:
ingest.max_pages_per_repocaps how deep the crawler paginates into any
one repository;ingest.max_db_size_mbpauses enqueuing/claiming once the database crosses a
configured size. Both live settings, both surfaced onGET /health/detailalongside current
DB size, so the ceiling is visible before it's hit. - Automated backups: a systemd timer running daily
pg_dumpwith 14-day retention on the
host. - The production repository catalog was populated for the first time: 287,998 eligible
repositories imported from the Kaggle metadata snapshot;ingestdhas been crawling live
since.
Shared client architecture
packages/api-client: one typed client for every API endpoint, used by both clients —
Issue/Contribution/Profiletypes,ApiErrorthat keepsstatusanddetailso a 401
is never confused with a 429 or a network failure,AbortController/cancellation support,
pluggable transport (fetchTransportfor web, an IPC-routed transport for desktop), and the
recommendations ETag/stale-cache fallback that used to be duplicated and drifting in both
clients.packages/ui: shared React components —Modal(body scroll lock, Escape-to-close,
focus trap, backdrop click — none of which either client had before),ConfirmDialog,
Toast,IssueCard/IssueList,WorkingList/SubmissionList,DifficultyTabs,
SectionTabs,Badge,ProgressBar,Skeleton.- Desktop renderer rewritten in React + TypeScript, replacing 1,780 lines of plain-JS
innerHTMLrebuilds. The old renderer blanked and refetched up to six endpoints from fifteen
call sites on nearly every interaction (including a single bookmark click) — the rewrite
moves to per-resource state with per-resource refetch, request sequencing so a fast
filter→page→search sequence can't let a stale response win, and real React reconciliation, so
scroll position now survives a list update. - Web's
App.tsxrebuilt onto the same shared packages, cut from 1,691 to roughly 1,100
lines of routing and web-specific glue — its hand-rolled issue card, login form, working/
submission lists, and detail modal are now the sharedpackages/uicomponents. - npm workspaces at the repo root (
web,desktop,packages/*) so@skillissues/*resolves
as a realnode_modulessymlink, with one lockfile for the whole tree.
Issue lifecycle
- Closed-issue detection, on demand only (no background sweep): the existing fork/clone
freshness check and a new GitHubissueswebhook handler persist a closed/reopened state
using GitHub's ownupdatedAt. A new batchedPOST /issues/freshnesslets a client check
only the handful of issues it's actually displaying. - Sticky saved issues carry their lifecycle (
none/working/submitted/merged) as a
badge; saved issues never disappear regardless of issue or contribution state. - Re-solve: starting a fresh attempt on an already-submitted or already-merged issue works
end to end in both clients, backed by the existing idempotent fork and partial-unique-index
contribution model. - Real
submitted_at, separate from the fork/start time (created_at) both clients were
previously mislabeling as the submission time. - A "View issue" action was added to working/submission cards on both clients (desktop's
working cards were missing it entirely).
Changed
- OAuth is now the only supported login path in both clients' UI; the legacy PAT-login
endpoint stays registered server-side but is disabled by default behind a live setting. FRONTEND_URLis parsed once as a proper multi-value list instead of being treated as a
single string everywhere, fixing cookieSecuredetection, the OAuth redirect target, and the
CORSAccess-Control-Allow-Originheader.- Issue-body "not ready yet" responses now carry a structured
{"status":"queued","reason":...}
instead of an ambiguous error shape, and the web client retries with real exponential backoff. - A failed
/saved,/working, or/contributionsfetch now keeps the previously loaded list
and shows a retry affordance instead of silently rendering empty — this previously made
real data look like it had disappeared whenever the client hit the unauthenticated rate limit. GET /recommendationsnow acceptsdifficulty/page/limitfilters through the shared
client, matching what the backend already supported.
Removed
- The co-contribution commit-trailer feature (
GET /github/co-contribution-trailerand both
clients' UI for it) — removed per product decision, not a regression. - The PAT login form from both clients' UI.
database/migrations/anddatabase/schema.sqlas a second, never-applied, out-of-sync
schema source — the embedded goose migrations underbackend/internal/migrate/migrations/
are the only schema source now.- The stale
k8s/manifests, which referenced images nothing built and committed a plaintext
database password.
Fixed
- The web production Docker image's
HEALTHCHECKalways failed: it resolvedlocalhostto
::1while nginx only listens on IPv4, so the check failed even while the container correctly
served real traffic. - CI never installed the shared
packages/markdownmodule's own dependencies, so every build
that exercised it was silently relying on a dev machine's leftovernode_modules. - The deploy workflow could run and attempt an SSH deploy even when its own CI job had failed,
caught only by an unrelated empty-tag guard; the condition now requires either an actual CI
success or an explicit manual rollback dispatch. cmd/ingestdlogging a prefix and length of the GitHub token at startup — removed outright;
credentials are never logged, not even partially.- A swallowed
QueryRowerror inGET /health/detailand a discardedUPDATEerror in
submitContribution, both now surfaced. - Account deletion (
authPurge) previously discarded its own delete errors and always reported
success; it now wraps the deletes in a transaction and returns the real error on failure.