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
18 changes: 17 additions & 1 deletion .claude/skills/backend-changes/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,20 @@ Features live under `Modules/<Feature>/`:
Commands/<Area>/ Queries/<Area>/ Controllers/
Domain/{Entities,Services}/ Events/ Enums/ Services/
DependencyInjection.cs
```
```

## Keep the live app working: add first, remove later

The production API is deployed **before** the new app goes to store review, so the previous app talks to the new API until the next force update. Every backend change must keep it working.

**Breaking — never in the same release as the change that replaces it:**

- **Response fields** removed, renamed, or changed in type or meaning.
- **Requests**: a query param or body field renamed or removed, or a new **required** one the old app doesn't send.
- **Endpoints** deleted or moved.
- **Enum values** renamed, removed or renumbered (adding one at the end is fine).
- **Validation or behaviour**: stricter rules, or a different result for the same request.

**How to change something anyway:** add the new one next to the old one, keep the old one working, and mark it `// Remove after force update to <version>`. A later release removes it, once the minimum supported version is past every app that used it. Optional new params with a default are fine; so is a new endpoint or field.

To check what the live app uses, look at the mobile code for the version in the latest published GitHub Release (`gh release view --json name -q .name`, e.g. "Release release/2026-10-17 — iOS 1.4.0 (161) · Android 1.4.0 (105)"), then `git grep <field> submitted/<version> -- blotztask-mobile`. Not `main`, and not simply the newest `submitted/*` tag: that tag is made at upload, so it can be a build still in review that no user has yet. Tag Release only publishes the release once both stores are live.
17 changes: 17 additions & 0 deletions .claude/skills/database-migrations/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,20 @@ Do NOT execute `dotnet ef migrations add` or `dotnet ef database update`. Always

- PascalCase, verb-first, describing the schema change — e.g. `AddLabelRelationToTaskItem`, `CreateTaskItemTable`, `RemoveSeedData`.
- Avoid vague names like `Update1` or `Fix`. Older entries like `createDuedateToTaskItem` are inconsistent — don't follow that casing.

## Add-only within a release

Migrations run in the production deploy, before the new app goes to review, with no down step. After one runs:

- **The previous app keeps working.** It talks to the new API straight away (see the backend-changes skill, "add first, remove later").
- **The previous API still runs on the new schema**, so a rollback to the last tag works.

So within one release a migration may only **add**: new tables, new nullable columns (or columns with a default), new indexes. These wait for a later release, after the old code that used them is gone from production:

- `DropTable`, `DropColumn`
- `RenameColumn`, `RenameTable`
- `AlterColumn` that changes type, length or nullability of a column the old code reads or writes

Moving data to a new shape: add the new table or column and copy the data in this migration (`migrationBuilder.Sql(...)`), keep the old one, drop it in a later release.

When you explain the migration to the user (step 1 above), say whether it is add-only. If it isn't, say which part must move to a later release.
2 changes: 2 additions & 0 deletions .claude/skills/pr-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ argument-hint: "[PR number, PR URL, or branch; optional, defaults to current bra

5. For Blotz-specific review, pay extra attention when the PR touches: auth/user scoping, mobile-backend DTO contract changes, date/timezone handling, recurring tasks, AI generation, AI quota usage, review reports, notifications, and EF/database changes.

**Backward compatibility is a major finding.** The production API ships before the new app is reviewed, so the previous app talks to it straight away. Flag any change the live app would notice: an endpoint, DTO field, request param, enum value or column removed, renamed or retyped, or a new required param. Check against the mobile code at the `submitted/<version>` tag of the latest published GitHub Release (`gh release view --json name -q .name`), not `main` and not the newest `submitted/*` tag, which may still be in review. Fix: keep the old one next to the new one, remove it in a later release (backend-changes skill).

6. **Decide whether re-testing is worth it — don't re-test by default.** Read the PR's `Verified:` line first.
- Clear proof, and a simple change (`L1`/`L2`) → trust it; don't re-run anything.
- Risky change (`L3`/`L4`), or the proof is thin, missing, or doesn't cover what the diff changes → re-run **only** the relevant tests: backend `cd blotztask-test && dotnet test --filter <class>`. A real-device run is expensive — ask the reviewer before starting one.
Expand Down
51 changes: 41 additions & 10 deletions .github/workflows/release-prod-app.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Prod - Submit app to the stores

# Button 1 of the two-button production release.
# Production release.
#
# Builds the production iOS + Android apps from main and uploads them - the iOS
# build to App Store Connect, the Android bundle to the Play production track -
Expand All @@ -11,11 +11,14 @@ name: Prod - Submit app to the stores
# upload lands as a draft (`releaseStatus: "draft"`) that only goes out when
# someone starts the rollout in the console.
#
# It deploys no backend on purpose. Store review sits in the middle of a
# production release, so by the time it passes, main has usually moved on.
# Button 2 (Prod - Ship backend) deploys the API from the `submitted/<version>`
# tag once the release is live in the stores, which is what keeps the API and the
# binary users installed on one commit.
# Then it deploys the production API from the same commit, before the build is
# sent for review. The deploy waits for approval on the `prod` environment.
#
# After that, by hand, once each store is live: in Azure set that platform's
# `AppVersion__<Ios|Android>__LatestVersion` to the new version (every release, it
# drives the optional "update available" prompt), and its
# `AppVersion__<Ios|Android>__MinimumSupportedVersion` too if this release forces
# the update. Run Tag Release on the `submitted/<version>` tag once both are live.

on:
workflow_dispatch:
Expand Down Expand Up @@ -165,9 +168,9 @@ jobs:
fetch-depth: 0
ref: main

# This tag is the whole point of button 1. It pins the commit that became the
# binaries now waiting in the store consoles, so that button 2 can deploy the
# API from it days later instead of from whatever main has become by then.
# This tag is the whole point of this workflow. It pins the commit that became the
# binaries now waiting in the store consoles, so that Tag Release can record the
# release against it days later instead of whatever main has become by then.
#
# Forced because a rejected review is resubmitted under the same version, and
# the tag should follow the code that was actually submitted last.
Expand All @@ -178,7 +181,7 @@ jobs:
git tag -f "$TAG" "$GITHUB_SHA"
git push --force origin "refs/tags/$TAG"
echo "### Tagged \`$TAG\` at \`$GITHUB_SHA\`" >> "$GITHUB_STEP_SUMMARY"
echo "Next: submit for review in App Store Connect, then once the release is live in both stores run **Prod - Ship backend** on this tag." >> "$GITHUB_STEP_SUMMARY"
echo "Next: approve the API deploy below. Once it is healthy, log in once with the prod build on TestFlight and check the task list loads, then submit for review. Once each store is live, set that platform's \`AppVersion__<Ios|Android>__LatestVersion\` in Azure to the new version, and its \`MinimumSupportedVersion\` too if this release forces the update. Once both are live, run **Tag Release** with commit \`$TAG\`." >> "$GITHUB_STEP_SUMMARY"

- uses: actions/download-artifact@v4
with:
Expand All @@ -201,3 +204,31 @@ jobs:
Tagged as `submitted/${{ needs.version.outputs.new_version }}`.

Merge to keep `eas.json` on `main` in step with what is in the stores.

deploy-api:
name: Deploy the API from the submitted commit
needs: tag-and-record
uses: ./.github/workflows/deploy-prod-api.yml
secrets: inherit
permissions:
id-token: write
contents: read

verify-api:
name: Health check
needs: deploy-api
runs-on: ubuntu-latest
steps:
- name: Check the production API is healthy
working-directory: .
run: |
set -euo pipefail
BODY=$(curl -fsS --retry 12 --retry-delay 15 --retry-all-errors \
https://app-blotz-task-api-prod.azurewebsites.net/health)
echo "/health returned: $BODY"
if [ "$BODY" != "Healthy" ]; then
echo "::error::production API is not healthy"
exit 1
fi
echo "### Production API deployed and healthy" >> "$GITHUB_STEP_SUMMARY"
echo "Log in once with the prod build on TestFlight, then submit for review." >> "$GITHUB_STEP_SUMMARY"
215 changes: 0 additions & 215 deletions .github/workflows/release-prod-backend.yml

This file was deleted.

Loading
Loading