Deploy web app to satsprice.app/app/ and link it from the website
Deploy website to GitHub Pages / deploy (push) Canceled after 0s

The GitHub Pages workflow now builds the Compose wasmJs web app and
publishes it alongside the marketing site, so it's reachable at
/app/. Add nav/hero/download links to it and list Web among the
supported platforms. Add website/serve-local.sh to reproduce the
production layout locally, and document the setup in README/AGENTS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 08:40:08 +03:00
co-authored by Claude Sonnet 5
parent e387f2906c
commit 1852d790ac
7 changed files with 129 additions and 9 deletions
+23 -1
View File
@@ -5,6 +5,14 @@ on:
branches: ["main"] branches: ["main"]
paths: paths:
- "website/**" - "website/**"
- "shared/**"
- "webApp/**"
- "gradle/**"
- "gradlew"
- "gradlew.bat"
- "build.gradle.kts"
- "settings.gradle.kts"
- "gradle.properties"
- ".github/workflows/pages.yml" - ".github/workflows/pages.yml"
workflow_dispatch: workflow_dispatch:
@@ -26,12 +34,26 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build web app
run: ./gradlew :webApp:wasmJsBrowserDistribution
- name: Assemble site
run: |
mkdir -p _site/app
cp -r website/. _site/
cp -r webApp/build/dist/wasmJs/productionExecutable/. _site/app/
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@v5 uses: actions/configure-pages@v5
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v3 uses: actions/upload-pages-artifact@v3
with: with:
path: "./website" path: "./_site"
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v4 uses: actions/deploy-pages@v4
+1
View File
@@ -5,6 +5,7 @@ xcodebuild*.log
.gradle/ .gradle/
.idea/ .idea/
/website/app/
.*.swp .*.swp
.*.swo .*.swo
.DS_Store .DS_Store
+29 -5
View File
@@ -8,11 +8,14 @@ real trial-and-error to get right.
## What this is ## What this is
SatsPrice is a Kotlin Multiplatform app, currently released on iOS, macOS, SatsPrice is a Kotlin Multiplatform app, currently released on iOS, macOS,
and Android (see README's Supported Platforms/Download and Install). Web and and Android (see README's Supported Platforms/Download and Install).
Desktop/JVM are real future release targets too`desktopApp` already has Desktop/JVM is a real future release target — `desktopApp` already has full
full native packaging configured (DMG/MSI/DEB, see its `build.gradle.kts`) — native packaging configured (DMG/MSI/DEB, see its `build.gradle.kts`) — but
but neither has a release/CI pipeline set up yet, so treat them as has no release/CI pipeline set up yet, so treat it as not-yet-shipped rather
not-yet-shipped rather than dev-only. The app converts between BTC, Sats, than dev-only. Web is a step ahead of Desktop: `.github/workflows/pages.yml`
builds `:webApp:wasmJsBrowserDistribution` and publishes it alongside the
marketing site to `satsprice.app/app/` on every push to `main` — see
"Website and web app deployment" below. The app converts between BTC, Sats,
and fiat currencies using live exchange rates (Coinbase, CoinGecko, or a and fiat currencies using live exchange rates (Coinbase, CoinGecko, or a
manually typed-in rate). manually typed-in rate).
@@ -149,6 +152,27 @@ fallback instead (`data/db/LocalStorageStores.kt`), which does survive page
reloads, just scoped to the browser profile/origin (cleared by clearing reloads, just scoped to the browser profile/origin (cleared by clearing
site data, not shared across browsers/devices). site data, not shared across browsers/devices).
## Website and web app deployment
`website/` is a static marketing site (plain HTML/CSS/JS, no build step of
its own) deployed to `satsprice.app` via GitHub Pages
(`.github/workflows/pages.yml`). That workflow does more than upload
`website/` as-is: it also runs `./gradlew :webApp:wasmJsBrowserDistribution`
and copies the result into `website/app/` before publishing, so the live
Compose web app is reachable at `satsprice.app/app/` and linked from the
site (nav, hero, download section) via `/app/`.
- `website/app/` is git-ignored — it's build output, generated fresh by CI
(and locally by the script below), never committed.
- To reproduce the production layout locally (site + web app together,
`/app/` links working): `./website/serve-local.sh [port]`. It builds the
wasmJs distribution, copies it into `website/app/`, and serves
`website/` with `python3 -m http.server`.
- If you change the Pages workflow, remember `paths:` in the trigger
includes `webApp/**`/`shared/**`/Gradle files, not just `website/**`
a shared-code change that affects the web app should also redeploy the
site.
## Testing and verification ## Testing and verification
- `shared/src/commonTest` holds the real unit test suite (pure Kotlin, - `shared/src/commonTest` holds the real unit test suite (pure Kotlin,
+4 -1
View File
@@ -27,7 +27,7 @@ height="70">](https://github.com/tyiu/sats-price/releases)
## Supported Platforms ## Supported Platforms
iOS 16.0+ • macOS 13.0+ • Android 7.0+ iOS 16.0+ • macOS 13.0+ • Android 7.0+ • [Web](https://satsprice.app/app/) (any modern browser)
## Screenshots ## Screenshots
@@ -75,6 +75,9 @@ options:
- Wasm target (faster, modern browsers): `./gradlew :webApp:wasmJsBrowserDevelopmentRun` - Wasm target (faster, modern browsers): `./gradlew :webApp:wasmJsBrowserDevelopmentRun`
- JS target (slower, supports older browsers): `./gradlew :webApp:jsBrowserDevelopmentRun` - JS target (slower, supports older browsers): `./gradlew :webApp:jsBrowserDevelopmentRun`
- iOS app: open the [/iosApp](./iosApp) directory in Xcode and run it from there. - iOS app: open the [/iosApp](./iosApp) directory in Xcode and run it from there.
- [Website](./website) (with the web app built and served at `/app/`, matching production):
`./website/serve-local.sh` (serves at http://localhost:8000 by default; pass a port number to
override)
### Running tests ### Running tests
+20 -2
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SatsPrice — Bitcoin Price &amp; Sats Converter</title> <title>SatsPrice — Bitcoin Price &amp; Sats Converter</title>
<meta name="description" content="SatsPrice converts Bitcoin, satoshis, and fiat currency in real time, sourced from Coinbase and CoinGecko. Free and open source for Android, iOS, and macOS." /> <meta name="description" content="SatsPrice converts Bitcoin, satoshis, and fiat currency in real time, sourced from Coinbase and CoinGecko. Free and open source for Android, iOS, macOS, and Web." />
<link rel="icon" href="assets/app-icon.png" type="image/png" /> <link rel="icon" href="assets/app-icon.png" type="image/png" />
<link rel="stylesheet" href="styles.css" /> <link rel="stylesheet" href="styles.css" />
</head> </head>
@@ -22,6 +22,7 @@
<a href="#features">Features</a> <a href="#features">Features</a>
<a href="#platforms">Screenshots</a> <a href="#platforms">Screenshots</a>
<a href="#download">Download</a> <a href="#download">Download</a>
<a href="/app/" target="_blank" rel="noopener">Web App</a>
<a href="https://github.com/tyiu/sats-price" target="_blank" rel="noopener">Code</a> <a href="https://github.com/tyiu/sats-price" target="_blank" rel="noopener">Code</a>
</nav> </nav>
@@ -37,6 +38,7 @@
<a href="#features">Features</a> <a href="#features">Features</a>
<a href="#platforms">Screenshots</a> <a href="#platforms">Screenshots</a>
<a href="#download">Download</a> <a href="#download">Download</a>
<a href="/app/" target="_blank" rel="noopener">Web App</a>
<a href="https://github.com/tyiu/sats-price" target="_blank" rel="noopener">GitHub</a> <a href="https://github.com/tyiu/sats-price" target="_blank" rel="noopener">GitHub</a>
</div> </div>
</header> </header>
@@ -60,6 +62,11 @@
<a href="#platforms" class="btn btn-ghost">See screenshots</a> <a href="#platforms" class="btn btn-ghost">See screenshots</a>
</div> </div>
<p class="hero-webapp">
Don't want to install anything?
<a href="/app/" target="_blank" rel="noopener">Try it in your browser →</a>
</p>
<div class="platform-pills"> <div class="platform-pills">
<span class="pill"> <span class="pill">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.05 12.536c-.03-3.06 2.5-4.53 2.61-4.6-1.42-2.08-3.63-2.37-4.42-2.4-1.88-.19-3.68 1.11-4.63 1.11-.96 0-2.42-1.09-3.98-1.06-2.05.03-3.94 1.19-5 3.02-2.13 3.7-.55 9.18 1.53 12.18 1.02 1.47 2.23 3.11 3.82 3.05 1.53-.06 2.11-.99 3.96-.99 1.84 0 2.37.99 3.98.96 1.65-.03 2.69-1.5 3.7-2.98 1.17-1.71 1.65-3.37 1.67-3.45-.04-.02-3.2-1.23-3.24-4.84zM14.15 3.6c.84-1.02 1.41-2.43 1.25-3.6-1.22.06-2.69.84-3.55 1.85-.78.9-1.46 2.34-1.28 3.57 1.2.09 2.75-.61 3.58-1.82z"/></svg> <svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.05 12.536c-.03-3.06 2.5-4.53 2.61-4.6-1.42-2.08-3.63-2.37-4.42-2.4-1.88-.19-3.68 1.11-4.63 1.11-.96 0-2.42-1.09-3.98-1.06-2.05.03-3.94 1.19-5 3.02-2.13 3.7-.55 9.18 1.53 12.18 1.02 1.47 2.23 3.11 3.82 3.05 1.53-.06 2.11-.99 3.96-.99 1.84 0 2.37.99 3.98.96 1.65-.03 2.69-1.5 3.7-2.98 1.17-1.71 1.65-3.37 1.67-3.45-.04-.02-3.2-1.23-3.24-4.84zM14.15 3.6c.84-1.02 1.41-2.43 1.25-3.6-1.22.06-2.69.84-3.55 1.85-.78.9-1.46 2.34-1.28 3.57 1.2.09 2.75-.61 3.58-1.82z"/></svg>
@@ -73,6 +80,10 @@
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.6 9.48l1.84-3.18c.16-.31.04-.69-.26-.85-.29-.15-.65-.06-.83.22l-1.88 3.24a11.43 11.43 0 0 0-8.94 0L5.65 5.67c-.18-.28-.54-.37-.83-.22-.3.16-.42.54-.26.85L6.4 9.48C3.3 11.25 1.28 14.44 1 18h22c-.28-3.56-2.3-6.75-5.4-8.52zM7 15.25a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5zm10 0a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5z"/></svg> <svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.6 9.48l1.84-3.18c.16-.31.04-.69-.26-.85-.29-.15-.65-.06-.83.22l-1.88 3.24a11.43 11.43 0 0 0-8.94 0L5.65 5.67c-.18-.28-.54-.37-.83-.22-.3.16-.42.54-.26.85L6.4 9.48C3.3 11.25 1.28 14.44 1 18h22c-.28-3.56-2.3-6.75-5.4-8.52zM7 15.25a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5zm10 0a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5z"/></svg>
Android Android
</span> </span>
<span class="pill">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
Web
</span>
</div> </div>
<div class="trust-row"> <div class="trust-row">
@@ -162,7 +173,7 @@
<div class="feature-card wide"> <div class="feature-card wide">
<div class="icon">💻</div> <div class="icon">💻</div>
<h3>Native on every platform</h3> <h3>Native on every platform</h3>
<p>SwiftUI on iOS and macOS, Compose Multiplatform on Android — built with Kotlin Multiplatform to feel right at home on each device, not like a port.</p> <p>SwiftUI on iOS and macOS, Compose Multiplatform on Android and Web — built with Kotlin Multiplatform to feel right at home on each device, not like a port.</p>
</div> </div>
<div class="feature-card"> <div class="feature-card">
@@ -279,10 +290,17 @@
</a> </a>
</div> </div>
<div class="webapp-divider"><span>or</span></div>
<div class="webapp-cta">
<a href="/app/" class="btn btn-ghost" target="_blank" rel="noopener">Open the web app — no install required</a>
</div>
<div class="version-row"> <div class="version-row">
<span class="pill">🍎 iOS 16.0+</span> <span class="pill">🍎 iOS 16.0+</span>
<span class="pill">🖥️ macOS 13.0+</span> <span class="pill">🖥️ macOS 13.0+</span>
<span class="pill">🤖 Android 7.0+</span> <span class="pill">🤖 Android 7.0+</span>
<span class="pill">🌐 Web — any modern browser</span>
</div> </div>
<p class="oss-note"> <p class="oss-note">
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Builds the web app, drops it into website/app/, and serves the whole
# website locally so the /app/ links behave like they do in production.
set -euo pipefail
PORT="${1:-8000}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
echo "Building web app (wasmJs)..."
"$REPO_ROOT/gradlew" -p "$REPO_ROOT" :webApp:wasmJsBrowserDistribution
echo "Copying web app into website/app/..."
mkdir -p "$SCRIPT_DIR/app"
cp -r "$REPO_ROOT/webApp/build/dist/wasmJs/productionExecutable/." "$SCRIPT_DIR/app/"
echo "Serving website at http://localhost:$PORT/ (web app at http://localhost:$PORT/app/)"
cd "$SCRIPT_DIR" && python3 -m http.server "$PORT"
+34
View File
@@ -268,6 +268,20 @@ h1, h2, h3, h4 {
margin-top: 32px; margin-top: 32px;
} }
.hero-webapp {
margin-top: 18px;
font-size: 14px;
color: var(--text-faint);
}
.hero-webapp a {
color: var(--gold);
font-weight: 600;
text-decoration: none;
}
.hero-webapp a:hover { text-decoration: underline; }
.platform-pills { .platform-pills {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -652,6 +666,26 @@ h1, h2, h3, h4 {
.badge-link.obtainium img, .badge-link.obtainium img,
.badge-link.github img { height: 56px; } .badge-link.github img { height: 56px; }
.webapp-divider {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
margin-top: 30px;
color: var(--text-faint);
font-size: 13px;
}
.webapp-divider::before,
.webapp-divider::after {
content: "";
height: 1px;
width: 80px;
background: var(--border);
}
.webapp-cta { margin-top: 18px; }
.version-row { .version-row {
margin-top: 38px; margin-top: 38px;
display: flex; display: flex;