Skip to content
Merged
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
47 changes: 47 additions & 0 deletions .github/workflows/bootstrap_glucose_guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Bootstrap Glucose Guard
run-name: Bootstrap Glucose Guard (${{ github.ref_name }})
on:
workflow_dispatch:
push:
branches:
- main
- cursor/glucose-guard-branding-05a7
paths:
- "glucose-guard-design/**"
- "Scripts/publish_glucose_guard_design.sh"
- "Scripts/apply_glucose_guard_branding.sh"
- ".github/workflows/bootstrap_glucose_guard.yml"

permissions:
contents: read
actions: write

jobs:
publish-design-and-build:
runs-on: ubuntu-latest
env:
GH_PAT: ${{ secrets.GH_PAT }}
GH_TOKEN: ${{ secrets.GH_PAT }}
steps:
- name: Checkout Loop fork
uses: actions/checkout@v5

- name: Require GH_PAT
run: |
if [ -z "${GH_PAT}" ]; then
echo "Secret GH_PAT is missing. Add the same PAT used for Loop browser builds." >&2
exit 1
fi

- name: Publish iDustbin/glucoseguard
env:
GLUCOSE_GUARD_DESIGN_REPO: glucoseguard
run: ./Scripts/publish_glucose_guard_design.sh

- name: Enable and start 4. Build Loop
run: |
set -euo pipefail
gh workflow enable "4. Build Loop" || true
gh workflow run "4. Build Loop" --ref "${GITHUB_REF_NAME}"
echo "Dispatched 4. Build Loop on ${GITHUB_REF_NAME}"
echo "That job applies icons, GlucoseGuard name, and the Figma mobile theme from iDustbin/glucoseguard, then Fastlane/TestFlight."
11 changes: 6 additions & 5 deletions .github/workflows/build_loop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,10 @@ jobs:

# This syncs any target branch to upstream branch of the same name
- name: Sync upstream changes
if: | # do not run the upstream sync action on the upstream repository
if: | # skip on feature branches so a TestFlight test build is not reset to LoopKit
steps.workflow-permission.outputs.has_permission == 'true' &&
vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'LoopKit'
vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'LoopKit' &&
(github.ref_name == 'main' || github.ref_name == 'dev')
id: sync
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.2
with:
Expand Down Expand Up @@ -190,8 +191,8 @@ jobs:

# LoopWorkspace patches
# -applies any patches located in the LoopWorkspace/patches/ directory
if $(ls ./patches/* &> /dev/null); then
git apply ./patches/* --allow-empty -v --whitespace=fix
if $(ls ./patches/*.patch &> /dev/null); then
git apply ./patches/*.patch --allow-empty -v --whitespace=fix
fi

# Submodule Loop patches:
Expand All @@ -215,7 +216,7 @@ jobs:
env:
GH_PAT: ${{ secrets.GH_PAT }}
GLUCOSE_GUARD_DESIGN_OWNER: iDustbin
GLUCOSE_GUARD_DESIGN_REPO: glucose-guard-design
GLUCOSE_GUARD_DESIGN_REPO: glucoseguard
GLUCOSE_GUARD_DESIGN_REF: main
run: ./Scripts/apply_glucose_guard_branding.sh

Expand Down
3 changes: 2 additions & 1 deletion LoopConfigOverride.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
// Override this if you don't want the default com.${DEVELOPMENT_TEAM}.loopkit that loop uses
// MAIN_APP_BUNDLE_IDENTIFIER = com.myname.loop

// Customize this to change the app name displayed
// Home-screen name. Source of truth is also
// glucose-guard-design/ios/display_name.xcconfig (published to iDustbin/glucoseguard).
MAIN_APP_DISPLAY_NAME = GlucoseGuard

// Customize this to change the URL to open Loop to something other than the display name
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

This fork builds **Glucose Guard** on top of [LoopKit/LoopWorkspace](https://github.com/LoopKit/LoopWorkspace).

This step is **iOS only**: GitHub Actions sync from upstream Loop, apply Glucose
Guard icons and the display name, then publish to the existing TestFlight
account. Branding lives in `glucose-guard-design/` (and later in
[iDustbin/glucose-guard-design](https://github.com/iDustbin/glucose-guard-design)
when that repo exists). There is no web or Kubernetes app in this step.
Two GitHub pieces, then one iOS build:

1. This repo is already the fork of [LoopKit/LoopWorkspace](https://github.com/LoopKit/LoopWorkspace).
2. [iDustbin/glucoseguard](https://github.com/iDustbin/glucoseguard) holds logo, icons, the **GlucoseGuard** display name, and the Figma mobile theme. The **Bootstrap Glucose Guard** Action publishes that repo (via `GH_PAT`) and then starts **4. Build Loop**.
3. Build Loop clones the design repo, applies branding plus native Xcode/SwiftUI Figma screens, and uploads to the existing TestFlight app (`com.TEAMID.loopkit.Loop`). No new Apple App ID.

Run **Bootstrap Glucose Guard** (or push to this branch) after `GH_PAT` is set. Then install the new TestFlight build to see icon, name, and the Glucose Guard home chrome.

For a local Xcode icon/name refresh after an upstream pull:

Expand Down
111 changes: 104 additions & 7 deletions Scripts/apply_glucose_guard_branding.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#!/usr/bin/env bash
# Pull Glucose Guard branding from iDustbin/glucose-guard-design and apply
# Pull Glucose Guard branding from iDustbin/glucoseguard and apply
# it to this LoopWorkspace checkout. Used after upstream LoopKit sync so
# icons and the display name survive a fork reset.
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DESIGN_OWNER="${GLUCOSE_GUARD_DESIGN_OWNER:-iDustbin}"
DESIGN_REPO="${GLUCOSE_GUARD_DESIGN_REPO:-glucose-guard-design}"
DESIGN_REPO="${GLUCOSE_GUARD_DESIGN_REPO:-glucoseguard}"
DESIGN_REF="${GLUCOSE_GUARD_DESIGN_REF:-main}"
LOCAL_DESIGN="${GLUCOSE_GUARD_DESIGN_PATH:-}"
CLONE_DIR=""
Expand All @@ -29,25 +29,35 @@ resolve_source() {
fi

local bundled="${ROOT}/glucose-guard-design"
local url="https://github.com/${DESIGN_OWNER}/${DESIGN_REPO}.git"
CLONE_DIR="$(mktemp -d)"
export GIT_TERMINAL_PROMPT=0

echo "Cloning ${DESIGN_OWNER}/${DESIGN_REPO}@${DESIGN_REF}" >&2
set +e
if [[ -n "${GH_PAT:-}" ]]; then
git -c "http.extraHeader=Authorization: Bearer ${GH_PAT}" \
clone --depth 1 --branch "${DESIGN_REF}" "${url}" "${CLONE_DIR}"
git clone --depth 1 --branch "${DESIGN_REF}" \
"https://x-access-token:${GH_PAT}@github.com/${DESIGN_OWNER}/${DESIGN_REPO}.git" \
"${CLONE_DIR}"
else
git clone --depth 1 --branch "${DESIGN_REF}" "${url}" "${CLONE_DIR}"
git clone --depth 1 --branch "${DESIGN_REF}" \
"https://github.com/${DESIGN_OWNER}/${DESIGN_REPO}.git" \
"${CLONE_DIR}"
fi
local clone_status=$?
set -e

if [[ "${clone_status}" -eq 0 && -d "${CLONE_DIR}/ios" ]]; then
echo "Cloned https://github.com/${DESIGN_OWNER}/${DESIGN_REPO} @ $(git -C "${CLONE_DIR}" rev-parse --short HEAD)" >&2
printf '%s\n' "${CLONE_DIR}"
return
fi

# In CI the design repo must be used. Local Xcode can fall back to the bundle.
if [[ -n "${GH_PAT:-}" ]]; then
echo "Failed to clone https://github.com/${DESIGN_OWNER}/${DESIGN_REPO}. Not using the bundled fallback in CI." >&2
exit 1
fi

echo "Remote ${DESIGN_OWNER}/${DESIGN_REPO} is not available; using bundled glucose-guard-design." >&2
rm -rf "${CLONE_DIR}"
CLONE_DIR=""
Expand All @@ -56,7 +66,7 @@ resolve_source() {
return
fi

echo "No Glucose Guard design source found. Create iDustbin/glucose-guard-design or set GLUCOSE_GUARD_DESIGN_PATH." >&2
echo "No Glucose Guard design source found. Create iDustbin/glucoseguard or set GLUCOSE_GUARD_DESIGN_PATH." >&2
exit 1
}

Expand Down Expand Up @@ -107,6 +117,58 @@ print(f"Set {replacement}")
PY
}

apply_localized_display_name() {
local name="$1"
python3 - "${ROOT}" "${name}" <<'PY'
import json
import sys
from pathlib import Path

root = Path(sys.argv[1])
name = sys.argv[2]
keys = ("CFBundleDisplayName", "CFBundleName")
updated = 0

loop_root = root / "Loop"
if not loop_root.is_dir():
print("Loop submodule is missing; cannot rewrite InfoPlist.xcstrings", file=sys.stderr)
sys.exit(1)

for path in loop_root.rglob("InfoPlist.xcstrings"):
data = json.loads(path.read_text(encoding="utf-8"))
strings = data.get("strings")
if not isinstance(strings, dict):
continue
changed = False
for key in keys:
entry = strings.get(key)
if not isinstance(entry, dict):
continue
locs = entry.get("localizations")
if not isinstance(locs, dict):
continue
for loc in locs.values():
if not isinstance(loc, dict):
continue
unit = loc.get("stringUnit")
if isinstance(unit, dict) and "value" in unit:
unit["value"] = name
changed = True
if changed:
path.write_text(
json.dumps(data, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
updated += 1
print(f"Set {name} in {path.relative_to(root)}")

if updated == 0:
print("No Loop InfoPlist.xcstrings files were updated", file=sys.stderr)
sys.exit(1)
print(f"Updated CFBundleDisplayName in {updated} string catalog(s)")
PY
}

SRC="$(resolve_source)"
copy_catalog \
"${SRC}/ios/OverrideAssetsLoop.xcassets" \
Expand All @@ -115,4 +177,39 @@ copy_catalog \
"${SRC}/ios/OverrideAssetsWatchApp.xcassets" \
"${ROOT}/OverrideAssetsWatchApp.xcassets"
apply_display_name "${SRC}/ios/display_name.xcconfig"
DISPLAY_NAME="$(python3 - "${SRC}/ios/display_name.xcconfig" <<'PY'
from pathlib import Path
import sys
name = "GlucoseGuard"
p = Path(sys.argv[1])
if p.is_file():
for line in p.read_text(encoding="utf-8").splitlines():
stripped = line.strip()
if stripped.startswith("MAIN_APP_DISPLAY_NAME"):
_, _, value = stripped.partition("=")
if value.strip():
name = value.strip()
break
print(name)
PY
)"
apply_localized_display_name "${DISPLAY_NAME}"

# Loop.xcconfig includes this file after the default MAIN_APP_DISPLAY_NAME = Loop.
printf '%s\n' \
"// Generated from glucose-guard-design/ios/display_name.xcconfig" \
"MAIN_APP_DISPLAY_NAME = ${DISPLAY_NAME}" \
> "${ROOT}/Loop/LoopOverride.xcconfig"
echo "Wrote Loop/LoopOverride.xcconfig (${DISPLAY_NAME})"

THEME_SCRIPT="${SRC}/ios/apply_theme.py"
if [[ ! -f "${THEME_SCRIPT}" ]]; then
THEME_SCRIPT="${ROOT}/glucose-guard-design/ios/apply_theme.py"
fi
if [[ ! -d "${ROOT}/Loop/LoopUI" ]]; then
echo "Loop submodule is missing; cannot apply Glucose Guard theme" >&2
exit 1
fi
python3 "${THEME_SCRIPT}" --loop-root "${ROOT}/Loop" --design-root "${SRC}"

echo "Glucose Guard branding applied from ${SRC}"
49 changes: 49 additions & 0 deletions Scripts/publish_glucose_guard_design.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Create or update iDustbin/glucoseguard from the bundled branding tree.
# Uses GH_PAT (never printed). Intended for GitHub Actions.
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
OWNER="${GLUCOSE_GUARD_DESIGN_OWNER:-iDustbin}"
REPO="${GLUCOSE_GUARD_DESIGN_REPO:-glucoseguard}"
SRC="${ROOT}/glucose-guard-design"

if [[ -z "${GH_PAT:-}" ]]; then
echo "GH_PAT is unset. Add the Loop browser-build PAT as a repo secret." >&2
exit 1
fi

if [[ ! -d "${SRC}/ios" ]]; then
echo "Missing ${SRC}/ios" >&2
exit 1
fi

export GH_TOKEN="${GH_PAT}"
export GH_PROMPT_DISABLED=1
export GIT_TERMINAL_PROMPT=0

if ! gh repo view "${OWNER}/${REPO}" >/dev/null 2>&1; then
echo "Creating ${OWNER}/${REPO}"
gh repo create "${OWNER}/${REPO}" \
--public \
--description "Glucose Guard iOS branding: logo, icons, display name" \
--disable-wiki
else
echo "${OWNER}/${REPO} already exists"
fi

work="$(mktemp -d)"
trap 'rm -rf "${work}"' EXIT
cp -R "${SRC}/." "${work}/"
cd "${work}"
git init -b main
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "sync iOS branding from LoopWorkspace"

# Token only in the remote URL for this process; do not echo the URL.
git remote add origin "https://x-access-token:${GH_PAT}@github.com/${OWNER}/${REPO}.git"
git push --force origin main

echo "Published branding to https://github.com/${OWNER}/${REPO}"
2 changes: 2 additions & 0 deletions glucose-guard-design/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
.idea/
.vscode/
*.log
__pycache__/
*.py[cod]
12 changes: 7 additions & 5 deletions glucose-guard-design/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Glucose Guard Design

iOS branding for **Glucose Guard**: logo, color tokens, and Loop override app icons.
iOS branding for **Glucose Guard**: logo, color tokens, Loop override app icons,
and native Xcode/SwiftUI screens from the Figma mobile theme
(Today / Learning / Healthway / +, CGM and pump detail, A1C).

Step 1 is **iOS only**. The Loop fork
[iDustbin/LoopWorkspace](https://github.com/iDustbin/LoopWorkspace) syncs from
[LoopKit/LoopWorkspace](https://github.com/LoopKit/LoopWorkspace). GitHub Actions
then apply `ios/` (icons + display name) and publish to the existing TestFlight
account.
then apply `ios/` and publish to the existing TestFlight account.

## Layout

Expand All @@ -29,5 +30,6 @@ GLUCOSE_GUARD_DESIGN_PATH=/path/to/glucose-guard-design \
./Scripts/apply_glucose_guard_branding.sh
```

Until `iDustbin/glucose-guard-design` exists as its own GitHub repo, this tree
stays bundled inside the Loop fork so TestFlight still gets the Glucose Guard icon.
The GitHub source of truth is [iDustbin/glucoseguard](https://github.com/iDustbin/glucoseguard).
This folder is the copy that Bootstrap publishes there, and the local fallback
if the remote is not cloned yet.
2 changes: 1 addition & 1 deletion glucose-guard-design/branding/tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"idle": {
"color": "#F4F4F5",
"meaning": "White ring — awaiting a live connection (placeholder in step 1)"
"meaning": "White/aging ring — waiting for a fresh closed loop"
},
"disconnected": {
"color": "#F4333C",
Expand Down
Loading
Loading