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
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.0.14] - 2026-07-26

### Changed

- iPhone, iPad, and Android now open through a restrained native launch
transition: a compact centered sailboat mark on the platform-appropriate
light or dark background, followed by a short fade as soon as the gateway or
workspace has painted. The previous oversized full-screen artwork and
artificial 1.2-second logo hold are removed.

### Fixed

- App Store packaging now safely copies an exported IPA across filesystem
boundaries before its atomic final rename, so clean builds on the retained
APFS release volume no longer fail after a successful Xcode export.

## [0.0.13] - 2026-07-26

### Added
Expand Down Expand Up @@ -380,7 +396,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
notarization, stapled tickets, Gatekeeper verification, persistent
Application Support, and isolated Apple container machines.

[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.13...HEAD
[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.14...HEAD
[0.0.14]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.14
[0.0.13]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.13
[0.0.12]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.12
[0.0.11]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.11
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ A fresh data directory opens first-run setup. The source runtime defaults to
| `PORT` | `8123` | HTTP/WebSocket control-plane port. |
| `CTRL_DATA_DIR` | `./data` | Databases, routing state, uploads, and narrow workspace mirrors. |
| `HELM_CHANNEL_COMPUTER_BACKEND` | `apple` on macOS, `lxc` on Linux, `wsl` on Windows | Host isolation backend; `native` and `mock` are explicit development/test overrides. |
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.13` | Versioned channel-machine image contract. |
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.14` | Versioned channel-machine image contract. |

### Agent-first JSON CLI

Expand Down
Binary file modified android/app/src/main/res/drawable-land-hdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-land-mdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-land-xhdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-land-xxhdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-land-xxxhdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-port-hdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-port-mdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-port-xhdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-port-xxhdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-port-xxxhdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions android/app/src/main/res/layout/launch_screen.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/launch_background">

<ImageView
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="center"
android:contentDescription="@null"
android:scaleType="centerInside"
android:src="@drawable/splash_mark" />
</FrameLayout>
4 changes: 4 additions & 0 deletions android/app/src/main/res/values-night/colors.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="launch_background">#0B0C0F</color>
</resources>
4 changes: 4 additions & 0 deletions android/app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="launch_background">#F2EFE7</color>
</resources>
5 changes: 3 additions & 2 deletions android/app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@


<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item>
<item name="windowSplashScreenBackground">#08090c</item>
<item name="android:background">@color/launch_background</item>
<item name="windowSplashScreenBackground">@color/launch_background</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_android12</item>
<item name="postSplashScreenTheme">@style/AppTheme.NoActionBar</item>
</style>
</resources>
9 changes: 5 additions & 4 deletions capacitor.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@
"resizeOnFullScreen": true
},
"SplashScreen": {
"launchShowDuration": 1200,
"launchAutoHide": true,
"backgroundColor": "#08090c",
"launchShowDuration": 500,
"launchAutoHide": false,
"launchFadeOutDuration": 180,
"showSpinner": false,
"androidScaleType": "CENTER_CROP"
"androidScaleType": "CENTER_INSIDE",
"layoutName": "launch_screen"
},
"StatusBar": {
"overlaysWebView": true,
Expand Down
8 changes: 4 additions & 4 deletions ios/App/App.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -300,14 +300,14 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 13;
CURRENT_PROJECT_VERSION = 14;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.0.13;
MARKETING_VERSION = 0.0.14;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = com.gitcommit90.onehelm.mobile;
PRODUCT_NAME = 1Helm;
Expand All @@ -322,14 +322,14 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 13;
CURRENT_PROJECT_VERSION = 14;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.0.13;
MARKETING_VERSION = 0.0.14;
PRODUCT_BUNDLE_IDENTIFIER = com.gitcommit90.onehelm.mobile;
PRODUCT_NAME = 1Helm;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.906",
"green" : "0.937",
"red" : "0.949"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.059",
"green" : "0.047",
"red" : "0.043"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 20 additions & 7 deletions ios/App/App/Base.lproj/LaunchScreen.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,34 @@
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<imageView key="view" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" id="snD-IY-ifK">
<view key="view" contentMode="scaleToFill" id="snD-IY-ifK">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</imageView>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" translatesAutoresizingMaskIntoConstraints="NO" id="Rcb-1H-elm">
<rect key="frame" x="143.5" y="289.5" width="88" height="88"/>
<constraints>
<constraint firstAttribute="width" constant="88" id="UjV-Cx-W88"/>
<constraint firstAttribute="height" constant="88" id="a1H-2m-H88"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" name="LaunchBackground"/>
<constraints>
<constraint firstItem="Rcb-1H-elm" firstAttribute="centerX" secondItem="snD-IY-ifK" secondAttribute="centerX" id="8Pj-1h-cx1"/>
<constraint firstItem="Rcb-1H-elm" firstAttribute="centerY" secondItem="snD-IY-ifK" secondAttribute="centerY" id="Zkf-1H-cy1"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="Splash" width="1366" height="1366"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<image name="Splash" width="512" height="512"/>
<namedColor name="LaunchBackground">
<color red="0.9490196078431372" green="0.9372549019607843" blue="0.9058823529411765" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "1helm",
"productName": "1Helm",
"version": "0.0.13",
"version": "0.0.14",
"private": true,
"type": "module",
"license": "AGPL-3.0-only",
Expand Down
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@
</head>
<body class="h-screen w-screen overflow-hidden antialiased">
<div id="app" class="h-full w-full"></div>
<script type="module" src="/bundle.js?v=c8024e58a77b"></script>
<script type="module" src="/bundle.js?v=de69bf156a35"></script>
</body>
</html>
35 changes: 26 additions & 9 deletions scripts/generate-mobile-assets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,36 @@ import sharp from "sharp";

const root = resolve(import.meta.dirname, "..");
const source = join(root, "desktop", "icons", "1helm-macos-app-logo.jpg");
const dark = { r: 8, g: 9, b: 12, alpha: 1 };

async function icon(target, size) {
await mkdir(dirname(target), { recursive: true });
await sharp(source).resize(size, size, { fit: "cover" }).png({ compressionLevel: 9 }).toFile(target);
}

async function splash(target, width, height) {
const markSize = Math.round(Math.min(width, height) * 0.34);
const mark = await sharp(source)
.resize(markSize, markSize, { fit: "cover" })
async function markBuffer(size) {
const radius = Math.round(size * 0.22);
const mask = Buffer.from(`<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg"><rect width="${size}" height="${size}" rx="${radius}" fill="white"/></svg>`);
const artwork = await sharp(source).resize(size, size, { fit: "cover" }).png().toBuffer();
return sharp({ create: { width: size, height: size, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } } })
.composite([{ input: artwork }, { input: mask, blend: "dest-in" }])
.png({ compressionLevel: 9 })
.toBuffer();
}

async function launchMark(target, canvasSize, markSize = canvasSize) {
const mark = await markBuffer(markSize);
await mkdir(dirname(target), { recursive: true });
await sharp({ create: { width: canvasSize, height: canvasSize, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } } })
.composite([{ input: mark, left: Math.round((canvasSize - markSize) / 2), top: Math.round((canvasSize - markSize) / 2) }])
.png({ compressionLevel: 9 })
.toFile(target);
}

async function legacySplash(target, width, height) {
const markSize = Math.round(Math.min(width, height) * 0.18);
const mark = await markBuffer(markSize);
await mkdir(dirname(target), { recursive: true });
await sharp({ create: { width, height, channels: 4, background: dark } })
await sharp({ create: { width, height, channels: 4, background: { r: 242, g: 239, b: 231, alpha: 1 } } })
.composite([{ input: mark, left: Math.round((width - markSize) / 2), top: Math.round((height - markSize) / 2) }])
.png({ compressionLevel: 9 })
.toFile(target);
Expand Down Expand Up @@ -47,12 +62,14 @@ const androidSplashes = {
"drawable-port-xxhdpi/splash.png": [960, 1600],
"drawable-port-xxxhdpi/splash.png": [1280, 1920],
};
for (const [relative, [width, height]] of Object.entries(androidSplashes)) await splash(join(androidRes, relative), width, height);
for (const [relative, [width, height]] of Object.entries(androidSplashes)) await legacySplash(join(androidRes, relative), width, height);
await launchMark(join(androidRes, "drawable-nodpi", "splash_mark.png"), 256);
await launchMark(join(androidRes, "drawable-nodpi", "splash_android12.png"), 256, 96);

const iosAssets = join(root, "ios", "App", "App", "Assets.xcassets");
await icon(join(iosAssets, "AppIcon.appiconset", "AppIcon-512@2x.png"), 1024);
for (const filename of await readdir(join(iosAssets, "Splash.imageset"))) {
if (filename.endsWith(".png")) await splash(join(iosAssets, "Splash.imageset", filename), 2732, 2732);
if (filename.endsWith(".png")) await launchMark(join(iosAssets, "Splash.imageset", filename), 512);
}

console.log("Generated branded iOS and Android icons and launch screens.");
console.log("Generated branded mobile icons and compact launch marks.");
6 changes: 4 additions & 2 deletions scripts/package-ios-ipa.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env node
import { mkdtemp, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
import { copyFile, mkdtemp, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
import { spawnSync } from "node:child_process";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
Expand Down Expand Up @@ -37,7 +37,9 @@ try {
run("xcodebuild", ["-exportArchive", "-archivePath", archive, "-exportPath", exported, "-exportOptionsPlist", options, "-allowProvisioningUpdates"]);
const ipaName = (await readdir(exported)).find((name) => name.endsWith(".ipa"));
if (!ipaName) throw new Error("Xcode did not produce an IPA.");
await rename(join(exported, ipaName), candidate);
// Xcode's temp directory and a release volume can be different filesystems.
// Copy into the release directory before the same-volume atomic rename.
await copyFile(join(exported, ipaName), candidate);
const entries = spawnSync("unzip", ["-Z1", candidate], { encoding: "utf8" });
if (entries.status !== 0 || /(^|\n)(?:AGENTS\.md|.*\/AGENTS\.md)(?:\n|$)/.test(entries.stdout || "")) throw new Error("The IPA is invalid or contains host-local instructions.");
await rm(canonical, { force: true });
Expand Down
5 changes: 4 additions & 1 deletion src/client/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { openRoutingPopover, pushRoutingActivity } from "./routing.ts";
import { openOnboarding } from "./onboarding.ts";
import { defaultTerminalComputer, openTerminals, refitChannelTerminals, getTerminalChrome } from "./term.ts";
import { openCreateChannel, renderActivity, renderBoard, renderChannelSettings, renderFiles, renderGlobalThreads, renderMemory, renderNotes, renderTexts, renderThreads, type ChannelView } from "./channel.ts";
import { apiUrl, forgetMobileServer, getServerOrigin, isNativeMobile, serverAssetUrl, setMobileServer } from "./mobile.ts";
import { apiUrl, finishNativeLaunch, forgetMobileServer, getServerOrigin, isNativeMobile, serverAssetUrl, setMobileServer } from "./mobile.ts";

/** Per-channel layout bound to the user profile (server user_ui_state). */
type ChannelUiView = {
Expand Down Expand Up @@ -698,6 +698,7 @@ function renderAuth(): void {
pw.addEventListener("keydown", (ev) => { if ((ev as KeyboardEvent).key === "Enter") submit(); });
(isNativeMobile() && !server.value ? server : u).focus();
if (!isNativeMobile()) void renderAccessRequest(access);
finishNativeLaunch();
}

async function renderAccessRequest(container: HTMLElement): Promise<void> {
Expand Down Expand Up @@ -771,6 +772,7 @@ export function renderApp(): void {
focusedNote.node.focus({ preventScroll: true });
focusedNote.node.setSelectionRange(focusedNote.start, focusedNote.end);
}
finishNativeLaunch();
}

function setMobileMenu(open: boolean): void {
Expand Down Expand Up @@ -3128,4 +3130,5 @@ void initializeApiTransport().then(() => boot()).catch((error) => {
h("h1", { class: "font-display text-2xl text-fg" }, "1Helm could not open"),
h("p", { class: "text-sm leading-6 text-muted" }, (error as Error).message || "The device security store is unavailable."),
h("button", { class: "btn-primary", onclick: () => location.reload() }, "Try again"))));
finishNativeLaunch();
});
11 changes: 11 additions & 0 deletions src/client/mobile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Browser } from "@capacitor/browser";
import { Capacitor } from "@capacitor/core";
import { Keyboard, KeyboardResize } from "@capacitor/keyboard";
import { SecureStorage, KeychainAccess } from "@aparajita/capacitor-secure-storage";
import { SplashScreen } from "@capacitor/splash-screen";
import { StatusBar, Style } from "@capacitor/status-bar";

const SESSION_KEY = "session";
Expand All @@ -14,11 +15,21 @@ type StoredSession = { server: string; token: string };

const native = Capacitor.isNativePlatform();
let serverOrigin = "";
let launchFinishQueued = false;

export const isNativeMobile = (): boolean => native;
export const mobilePlatform = (): string => native ? Capacitor.getPlatform() : "web";
export const getServerOrigin = (): string => serverOrigin;

/** Reveal the first real native screen only after the WebView has painted it. */
export function finishNativeLaunch(): void {
if (!native || launchFinishQueued) return;
launchFinishQueued = true;
requestAnimationFrame(() => requestAnimationFrame(() => {
void SplashScreen.hide({ fadeOutDuration: 180 }).catch(() => undefined);
}));
}

export function normalizeServerOrigin(value: string): string {
const raw = value.trim();
if (!raw) throw new Error("Enter your 1Helm server address.");
Expand Down
2 changes: 1 addition & 1 deletion src/server/channel-computers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const APPLE_RUNTIME_VERSION = "1.1.0";
export const APPLE_RUNTIME_PACKAGE = `container-${APPLE_RUNTIME_VERSION}-installer-signed.pkg`;
export const APPLE_RUNTIME_URL = `https://github.com/apple/container/releases/download/${APPLE_RUNTIME_VERSION}/${APPLE_RUNTIME_PACKAGE}`;
export const APPLE_RUNTIME_SHA256 = "0ca1c42a2269c2557efb1d82b1b38ac553e6a3a3da1b1179c439bcee1e7d6714";
export const DEFAULT_CHANNEL_IMAGE = process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.13";
export const DEFAULT_CHANNEL_IMAGE = process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.14";
const CONTAINER_CANDIDATES = [process.env.HELM_CONTAINER_CLI, "/usr/local/bin/container", "/opt/homebrew/bin/container", "container"].filter(Boolean) as string[];
const LXC_RUNTIME_VERSION = "1helm-lxc-runtime-v1";
const LXC_HELPER_CANDIDATES = [
Expand Down
Loading
Loading