The Makepad Builder (tools/makepad_builder: the Windows download is makepad-builder.zip with the Builder's source; macOS and Linux run makepad.sh), the CI runner (tools/ci), cargo-makepad, the agents tool, the root workspace and the documents. Covers work 6f1e44649..2b1a41df4 outside platform, draw, widgets, libs, apps and examples: - wm: the Android super-app compiles tiles on the phone against the host engine - cargo-makepad: the super-app APK is packed by `android dyn-pack`, in Rust - widgets, storybook: the widget catalogue app, some eighty new widgets, a theme store with mixable style sheets, and the rule that a press belongs to - wm, widgets: the style tween starts with all eight weights, and the new style sheet takes the last number instead of macOS's - platform: the Android desk swipes at 120 Hz — offscreen render passes and framebuffers cached, SVG meshes kept, font families that complete - cef: a page's audio can be captured instead of played - Button: activate from the keyboard when focused (#1241) - libs/ai: exclude hub_ui and services from the AI workspace (#1242) - flow: the flow-ui app and the flowgraph canvas return to the workspace, with staged progress in libs/flow - svg: a stroke that bends tighter than its half width collapses its inner edge to the corner - the accent a name is written with stops deciding whether search finds it - macOS: keep the paint clock beating while the window is minimized (#1244) - Linux: only link libdrm for the direct backend (#1245) - cargo-makepad: find dependency crate dirs via cargo metadata (#1246) - app_main!: only ship the fonts an app declares (#1247) - ai stems: LaneDemixer, VocalsModel and the platform Stage origin/main needs - platform, widgets, wm: every Vulkan pass renders through a negative-height viewport again, so a Wayland or X11 window stands upright, and no capture - calendar: its own colour contrast is named explicitly, now that the theme store exports one under the same name through the widgets glob - widgets: the supersampled resolve and the map's shadow mask sample their textures as stored, like every other render texture - platform: every OpenGL texture pass stores top-left rows, custom cameras included, and culls with the winding its inverted projection gives - platform, wm, director: a hosted app's software frame arrives as a top-left texture and the window manager and the director carry no flip term at al - widgets, platform: the last twelve warnings go - The Builder compiles itself again from the Makepad tree Scope's release pins and removes the source snapshots nothing is built from any more - The regex engine accepts \b and \B, reports the earliest match so a scan can resume after it, and can decide word boundaries on ASCII so a code sear - workspace: Scope's settings carry the code map's tab width and the directories each prepared project hides from its map - AGENTS.md: a platform change made for one application needs the user's feedback and checks first, and app specifics never leak into the shared layer - wm: the desktop style menu lists each style once - cef, video, platform: what Stage's browser needs from the shared layers - macOS: re-arm the display links when a window leaves the Dock (#1251) - platform, wm: work builds again on iOS, tvOS and wasm, and the window manager is warning-free on Windows - ci: tools/ci, a Splash runtime that watches a branch and runs every ci.splash it finds - stitch: the crate says what the rest of the repository says about its license - ci: a run is one target dir and one cargo batch per target, and the wall draws - ci: the wall shows THIS run and is fit for an OLED - ci: untested is grey and nothing else - ci: warming fills the cache and blames nobody - Vulkan: stop printing the loader's startup narration by default (#1252) - ci: the terminal gets its pty helper, director is a desktop tool, and a refused input is asked again - platform: no warnings on tvOS and linux_direct, and a missing pty helper says so - apps: what the CI box found - ScrollBar: add `show_handle` for a view that scrolls without a grabbable bar (#1254) - cargo-makepad: keep the android SDK at a stable path (#1253) - ci: the window is a dashboard - Remove Flow, asset and VJ applications moved into Stage - ci: a stop or a restart is not a test result - ci: a quiet header, a build that visibly moves, and scripts that leave the machine alone - widgets: children a lookup discovers reach the dump, the snapshot and the flood searches - tests: the workspace suite, run as a whole for the first time, passes outside the example UI tests - platform: a hidden window wears no hands-off frame - examples: the UI tests pass, for the reasons they failed - ci: a library's warnings turn the workspace block yellow, and a desktop tool is not warmed for the web - libs: no warnings in the workspace check on any row - ci: the workspace script gives the wall back after warming - ci: a judgement is never lost to the way it was phrased, and every "retry" of the bridge is retried - tests: the last three failures of the CI box's night - Linux: fix window chrome button hovers and how maximized/fullscreen windows work (#1255) - ci: the header names the tip being tested - tools: makepad-screen is makepad-agents, and its binary is `agents` - gif: the crate's doc examples compile - ci: the tests are the platform's own, in release, in two minutes - Tooltip: position anchored tooltips in the same draw (#1256) - cef: a hosted browser survives its host's exit - ci: a card is as high as its content - tests: the three binaries over ten seconds in release come under it - widgets: let the host install script mods into every Splash isolate - widgets: a pooled test context forgets the last case's Escape claim - widgets: the pooled test context's resets replace the globals - wm: the shell menu says what it did, and the CI waits for that - platform: the storage module says how much a volume has free - git: the memory ledger can take what it is asked for - ci: a driven app takes every key once, and the storage module builds for the browser - RadioButton: fix its touch hover state and click-off behavior (#1258) - HtmlLink: fix its hover and pressed states (#1257) - RadioButton: take key focus on the click, not on the press (#1259) - script: re-entrant dispatch, thread index validation, Any-based handle downcasts, UTF-8 previews; regex: never_loop fix - script: port Octoscript WS1 — worklist equality with fuel/deadline/work bail, uncaught-error bail, allow_debug_output - script: port Octoscript parser/tokenizer/control-flow VM patches (ws2) - script: port Octoscript heap/string/array/object hardening onto upstream's allocation budget - script: port orphaned Octoscript VM hardening (execution caps, clear_type_methods, parser diagnostics) - script: keep silenced streaming evals running past uncaught errors - script: the Octoscript hardening keeps a Makepad host's semantics and its speed - fix(text): enable ttf-parser gvar-alloc for many-tuple variable fonts - feat(text): CoreText outline fallback for hvgl-only fonts (macOS) - draw text: the CoreText outline fallback is only ever resolved for a face whose outlines live solely in hvgl, and font-family diagnostics are the `f - mail: the phone layout lines up - clock: the phone layout fits both orientations - calendar, reminders, calculator, sheets: the phone layouts fit their space - platform, widgets, wm: a touch that is taken away is cancelled, never released - weather, notes, finance, photos, route: the phone layouts tidy up - files: the phone layout tidies up - wm: the phone shell's surfaces match the app and the grid - wm on Android: every app is its own process - clock: a cancelled release neither opens an alarm nor switches tab - platform, wm: hosted apps ask the WM for what they cannot do themselves - wm on Android: apps build on the phone - wm on Android: app transitions and gestures feel like the phone's - android: rotated Vulkan windows stop rebuilding their swapchain every frame, and hosted children get touch - vulkan (android): a window released on suspend is not released again - android: hosted children hand frames over with GPU fences and draw only when they have work - wm on Android: gestures and surfaces settle like the phone's - widgets: touch lists scroll with Android's physics - wm on Android: the shell follows the Pixel launcher's motion - wm on Android: Recents shows the apps over a receding home, and the home screen switches looks - wm on Android: the look switcher stays put, the iOS dock shows its icons, background apps follow a look change - task, cargo-makepad, widgets, audio: the task manager chooses its columns, graphs every process' network and disk traffic and installs itself as a D - agents: a user touching an app under test no longer stops the agent - flowgraph: ordered ports take many wires, sockets can override their icon, and the canvas embeds as a viewport - cef: captured frames and audio carry callback clocks and a navigation epoch - git: timings read a portable clock - ai: the hub's rig-fixture tests find the asset library without the asset client - ai llm: a Metal main buffer is filled in 32 MB chunks - diskmap: a scan lists each macOS directory in one getattrlistbulk call and classifies files without allocating or locking - files: the tile view's three projections are toolbar buttons - mail: the local view takes a theme-derived palette and line icons, search pages share records - director: agent lanes form a tree and Grok joins the usage bar - widgets, draw: skeuomorphic surfaces light each other through an optional relief buffer - widgets, platform: a texture can light the relief buffer, menus take colour chips, and a style reload recompiles changed shader functions - widgets: relief surfaces can travel like mechanical keys, and dark surfaces take less neighbour light - platform, widgets, audio_route, ai: window crossfades and whole-frame presents, caption controls that click, themed menus, a stereo-pair audio tap, - builder: the Builder TUI matches the new terminal design, Windows executables carry app icons, and every app has one - platform: Android builds without Vulkan compile again, and the font-selection test counts the hosted entry point - platform: the pipeline-skip repaint mark exists only on Apple, where Metal reads it - builder: the terminal scripts delete nothing and read top to bottom, and every Builder delete stays inside its own folder - files: the Files app deletes nothing and never overwrites - director: coding agents ask before acting unless you choose otherwise - sheets, score, git, home: saves cannot cut a file short or overwrite another one, and checkouts cannot write outside the repository - git: worktree status matches git on real repositories and can be cancelled - builder: one forward-only bar per setup component, green checks on finished setup rows, a black Builder window and a smaller Windows ZIP - builder: the GPU notice is remembered - cuda: builds link only the CUDA toolkit they are given, never a system install - builder: a refused email opens the editor again with what was typed and says why, and the app section is YOUR APPS - builder: a refused email says "Email not recognised" and keeps what was typed for fixing - platform (windows): every window keeps animating with several windows open - builder previews: long work gets a page of its own (steps as a checklist, the current one carrying its bar, footer working · ctrl+c stops), Account - builder: long work runs on its own page - builder: a first build resolves online, the Rust row and bars move when the work does, and the window fits the TUI - audio_route: one API on macOS, Windows and Linux, monitoring by default and processing only when asked, plus shaders that discard a value compile to - platform, widgets, ai: a restyle recompiles nothing it already has, shows only complete frames, and a window can own its caption - builder: Compile shows a real bar - builder: the compile line is the bar and n / total crates - builder: apps get the icon their package declares - builder: no Scope command - widgets: relief surfaces can swing their light toward a point - builder: a HEAD request is a HEAD - builder: the menu waits for a choice - builder: no menu flash after log-in - builder: wait out Windows security on fresh Rust - builder: the menu opens on the first of YOUR APPS - platform (windows): half-float RGBA textures have four channels - builder: CUDA whenever the machine has NVIDIA - audio_route example: print the samples that reached the processor each second - widgets, builder: the big widget families are features, all on by default, and the Builder takes none of them - platform (macos): the display link is kept until it is invalidated - ai hub: a local chat with no model says so once, plainly - builder: the Windows exe is built for size - windows: desktop apps open no console window, and still speak through pipes - ai: Claude Desktop drives any app with the F10 panel - Constrain Splash external I/O to the host service bridge (#1243) - PortalList: stop following the end when scrolling to an earlier item (#1261) - wm: starts on Windows - ci: a full disk empties the build output before the run, not every row after it - builder: an incremental compile's bar follows what really compiles - builder: the compile bar's total is the app's own crates - builder: menus wrap around - wm: children open no console windows on Windows - wm: the whole deck out of a Builder's source, compiled only when the person opens an app - builder: CUDA crates build in an installation whose path has a space - ai: Connect hands the .mcpb to Claude Desktop itself - wm: the clock and calendar know the date on Windows - platform (windows): a popup opened for the first time draws its rows - wm: opening an app rebuilds it when its binary is out of date; warm instances never build - builder: an app edited after its build shows as needing a compile - civil-time: one local wall clock for every app, and it knows the zone on Windows - platform + wm: a hosted child redraws at its tile's new size - platform (windows): a texture pass drawn before its window has a size is skipped, not a crash - draw: glass of a view that stopped drawing leaves the screen - calendar: the Calendars sheet is an opaque panel, and the wide layout does not float one over its sidebar - build: dev keeps line tables only, release is incremental without LTO, the parallel frontend is a documented local opt-in - cargo-makepad: a binary with several app_main! entry points bundles - script: #[derive(Script)] emits one helper call per field, and ScriptNew's default methods keep their bodies out of every type - widgets: the Widget lookup methods and with_script_vm_id are compiled once, not once per widget type - platform: studio-protocol no longer waits for script, so platform starts ~0.5 s earlier - ai-speech: makepad-ai-sfx (and with it ai-h3) is built only for IndexTTS - platform: an app's package dir, icons and bundle name rebuild only that app - script + wasm_bridge: no build script that reruns on every file - build: a private app cloned into apps/<name> joins this workspace, Stage first - build: the AI stack, csg, Scope and Source Library join the one workspace - builder: every app builds in the one Makepad workspace, one target directory per source snapshot - ai chat: the chat's backends are the CLI, MCP and cloud providers; the local model is the `localai` feature - apps: every app that hosts the chat has a `localai` feature for the local model, off unless the app's own job runs one - builder: time each install component and phase - builder: Build tools, Windows SDK, Rust and CUDA install side by side, each with its own bar - builder bootstrap: rustc, rust-std and cargo download and unpack side by side under one Rust bar - widgets: the library moves to makepad-widgets-core in widgets/core, and makepad-widgets becomes its front crate - widgets: the Window, widget tree and panel theme reach the tweaker, voice, AI slot and dock through hooks, not through their modules - widgets: every widget family is a crate of its own under widgets/families, and makepad-widgets links, re-exports and registers the ones its features - aichat, widgets: the chat reads the design feedback through the tweaker's hook, so linking the chat no longer needs the tweaker - apps, examples, libs: each crate builds only the widget families it uses, and the design overlay is each app's own default feature - builder: an app release builds without the app's development defaults, and the catalog names every feature a release ships with - widgets: the build scripts rerun on their own edits only, and makepad-widgets no longer reads MAKEPAD - livepipe: takes makepad-widgets without its default families, now that the AI stack is in the one workspace - builder: the catalog names the new localai defaults of route and ai-hub - feedback: Send feedback, a caption icon and a small panel that shows exactly what is sent - builder: an app it launches knows who the person is, for Send feedback - feedback: the panel's wording reads right and draws in any font - feedback: takes makepad-widgets without its default families - builder: clear build data deletes target/ itself and says why when it cannot - ai chat: a build without local AI starts on the first provider that answers here, not on "No model" - platform: MAKEPAD=gpusim builds again - cargo-makepad: a Windows desktop build links the app's icon into its own binary, not into RUSTFLAGS - thiserror: RUSTC_BOOTSTRAP no longer turns on the unstable generic member access API - builder: every end-user build uses rustc's parallel frontend, and falls back to one thread when it fails - builder: the Windows SDK downloads the 25 cabinets its MSIs name instead of all 149, no screen or log shows a \\?\ path, long status messages wrap, - builder: CUDA kernels compile in any install folder, and when they cannot the app is built without them - builder: makepad-builder.exe declares itself like a well-formed Windows program and stops a stalled build through a job object instead of taskkill - platform (macos): a contained panic no longer frees the windows under AppKit or leaves an app that ignores quit - builder: macOS and Linux run the Builder as one shell script, the same TUI as Windows, and nothing is compiled for it - builder + ai-cuda: the CUDA kernel progress shows in the Builder's Compile row, not scribbled over its screen - builder: an app built with CUDA starts on Windows - builder (windows): the downloaded exe is built large again - builder (unix): the email check takes real addresses - repo: no third-party comic archive, generated models or sample asset in the tree - remove big example media files - cargo_makepad: no stray KNMI radar frame in the Android Java sources - widgets: the empty parts of an app's caption bar drag the window again - ScrollBar: fade out when idle, like macOS overlay scrollers (#1264) - platform: every app builds the same `windows` crate - wm: in a Makepad Builder installation apps build through the Builder, however wm was started - platform, draw: web builds start again - platform: web text draws again - widgets: the keyboard reaches boxes and modals - widgets: a hover tooltip never sits under the mouse cursor - platform: clipboard_read, reading text or an image from the system clipboard - feedback: Send clipboard, and the dialog works from the keyboard - builder: the Windows Builder ships as source, runs in its own console, and apps compile with Rust's GNU toolchain or Microsoft's - platform: an app also finds its resource map in the builder folder beside it - win_resource: PNG decode and encode on makepad-fast-inflate instead of zune-png - builder: one folder layout everywhere, makepad-builder.zip with only the files a Windows build reads, a Rust download over parallel connections, and - builder (windows): the .bat runs from a folder with spaces and parentheses, and the ZIP always carries it with CRLF - builder: coding agents start from the macOS/Linux Builder, its compile bar has its end, the app rows say where each app is in a word or two, and Win - builder (unix): running curl | sh again opens the existing installation with its saved email - builder: log out from the Account row - builder: `makepad-builder build APP` compiles an app offline exactly as the Builder does Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
932 lines
39 KiB
Bash
932 lines
39 KiB
Bash
#!/bin/sh
|
||
# PREVIEW of the Windows Builder TUI (makepad-builder.exe), as a shell script
|
||
# so it runs here. The real one is Rust (tools/makepad_builder/src/tui.rs);
|
||
# this is its visual spec. Everything is simulated: nothing is downloaded,
|
||
# installed or written; the License agreements page really opens links.
|
||
#
|
||
# Windows differences from macOS: the folder is wherever the ZIP was unzipped
|
||
# (portable, no question), setup installs the Visual Studio Build Tools,
|
||
# Windows SDK and Rust itself (never a system Rust), CUDA is optional for
|
||
# NVIDIA GPUs (AI features in Makepad Amp), and apps are .exe files you pin
|
||
# to the taskbar.
|
||
#
|
||
# sh makepad-windows-preview.sh first run
|
||
# sh makepad-windows-preview.sh --installed an existing installation
|
||
# sh makepad-windows-preview.sh --resume an earlier run stopped mid-download
|
||
# sh makepad-windows-preview.sh --no-nvidia a PC without an NVIDIA GPU
|
||
# sh makepad-windows-preview.sh --free an email without licenses
|
||
set -eu
|
||
|
||
demo=first demo_rust=none demo_free=no email= command= nvidia=yes
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--installed) demo=installed email=rik2@n4.io ;;
|
||
--resume) demo=resume email=rik2@n4.io ;;
|
||
--no-nvidia) nvidia=no ;;
|
||
--free) demo_free=yes ;;
|
||
--email) shift; email=$1 ;;
|
||
-h|--help) sed -n '2,17p' "$0"; exit 0 ;;
|
||
build|run|list|env|agent-context) command=$1; shift; break ;;
|
||
*) printf 'unknown option %s\n' "$1" >&2; exit 1 ;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
# ------------------------------------------------------------------ model ---
|
||
# App states, all derived from the folder on every start:
|
||
# new nothing downloaded -> download, compile, run
|
||
# partial a download was interrupted -> resume, compile, run
|
||
# compile source is newer than the build -> compile, run
|
||
# ready built from the current source -> run
|
||
# The live version reads these markers:
|
||
# makepad.json the account email
|
||
# selected-rust "local" or the system sysroot
|
||
# toolchain/rust/<v>/.ready local Rust completely unpacked
|
||
# downloads/<repo>.part an interrupted download (curl -C - resumes)
|
||
# sources/<repo>/.verified source checked against the catalog sha256
|
||
# <Title>.app/.../build-source the source commit the bundle was built from
|
||
# .lock/pid another run is active (stale ones cleared)
|
||
root="C:\\Users\\$USER\\Downloads\\makepad-commercial"
|
||
cuda=none cuda_pin=12.8 gpu=ok
|
||
rust_pin=1.92.0
|
||
system_rust=1.93.1
|
||
[ "$demo_rust" = found ] || system_rust=
|
||
licensed=unknown
|
||
checked=
|
||
message=
|
||
|
||
get() { eval "printf '%s' \"\${st_$1:-new}\""; }
|
||
put() { eval "st_$1=\$2"; }
|
||
|
||
# Licensed apps come from the VPS catalog: id|title|license|megabytes|source
|
||
fetch_licenses() {
|
||
if [ "$demo_free" = yes ]; then licenses=; else licenses='scope|Makepad Scope|commercial|214|sources/scope
|
||
amp|Makepad Amp|beta|188|sources/stage/apps/amp'; fi
|
||
}
|
||
# The free apps in the public Makepad repository (apps.json in the live one).
|
||
free_apps='wm|Makepad WM
|
||
mail|Mail
|
||
notes|Notes
|
||
calendar|Calendar
|
||
terminal|Terminal
|
||
files|Files
|
||
photos|Photos
|
||
sheets|Sheets
|
||
pdf|PDF
|
||
mixer|Mixer
|
||
clock|Clock
|
||
weather|Weather
|
||
calculator|Calculator
|
||
reminders|Reminders
|
||
finance|Finance
|
||
video|Video
|
||
browser|Browser
|
||
fab|Fab
|
||
director|Director
|
||
route|Route'
|
||
|
||
scan() {
|
||
rust=local
|
||
case "$demo" in
|
||
first) rust=none ;;
|
||
installed) put scope ready; put amp ready; put makepad ready; put wm ready; put mail ready ;;
|
||
resume) put scope partial ;;
|
||
esac
|
||
# Free apps share one Makepad download; built ones are ready, the rest
|
||
# compile on first run.
|
||
src=$(get makepad)
|
||
for id in $(printf '%s\n' "$free_apps" | cut -d'|' -f1); do
|
||
if [ "$src" = new ]; then put "$id" new; elif [ "$(get "$id")" != ready ]; then put "$id" compile; fi
|
||
done
|
||
}
|
||
|
||
# ------------------------------------------------------- agent interface ---
|
||
# The environment every build uses, the TUI's and an agent's alike: the
|
||
# selected compiler first on PATH, caches and output inside the folder.
|
||
print_env() {
|
||
if [ "${rust:-local}" = system ]; then bin="$HOME/.rustup/toolchains/stable-aarch64-apple-darwin/bin"; else bin="$root/toolchain/rust/$rust_pin/bin"; fi
|
||
cat <<EOF
|
||
export PATH="$bin:\$PATH"
|
||
export CARGO_HOME="$root/cargo-home"
|
||
export CARGO_TARGET_DIR="$root/target"
|
||
export RUSTUP_TOOLCHAIN= RUSTC_WRAPPER= RUSTFLAGS=
|
||
# One shared target/ lets every app reuse the crates the others compiled.
|
||
# No debug info or separate debug files; stripped binaries.
|
||
export CARGO_PROFILE_RELEASE_DEBUG=0 CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=off CARGO_PROFILE_RELEASE_STRIP=true
|
||
EOF
|
||
}
|
||
|
||
# Written to AGENTS.md (and CLAUDE.md) in the folder before an agent opens,
|
||
# so Claude, Codex and Grok all read it on start.
|
||
print_agent_context() {
|
||
cat <<EOF
|
||
# Makepad apps in this folder
|
||
|
||
Everything here is Makepad applications as Rust source. The person using this
|
||
folder wants you to change these apps and rebuild them.
|
||
|
||
## Build and run
|
||
|
||
Always use the \`makepad\` command in this folder; do not call cargo directly.
|
||
It uses this folder's compiler (a private Rust $rust_pin in toolchain/, or the
|
||
system Rust recorded in selected-rust), keeps caches and build output in this
|
||
folder, and rebuilds the macOS app bundle so Dock icons keep working.
|
||
|
||
./makepad build <app> compile after you change its source
|
||
./makepad run <app> compile if needed, then open <App>.app
|
||
./makepad list every app, its source folder and state
|
||
|
||
A build prints compiler errors in full; fix them and build again. If you need
|
||
cargo itself (tests, clippy), first run: eval "\$(./makepad env)"
|
||
|
||
## Apps
|
||
|
||
| app | name | source |
|
||
|-----|------|--------|
|
||
EOF
|
||
printf '%s\n' "$licenses" | while IFS='|' read -r id title _ _ src; do
|
||
printf '| %s | %s | %s |\n' "$id" "$title" "$src"
|
||
done
|
||
printf '%s\n' "$free_apps" | while IFS='|' read -r id title; do
|
||
printf '| %s | %s | sources/makepad/apps/%s |\n' "$id" "$title" "$id"
|
||
done
|
||
cat <<EOF
|
||
|
||
Makepad itself (widgets, platform, shaders) is in sources/makepad; read its
|
||
AGENTS.md before changing it. Each repository keeps its own instructions.
|
||
|
||
## After an update
|
||
|
||
Updates never merge. When an app you changed gets a new release, the old
|
||
changes are saved first and the app is replaced by the clean new source:
|
||
|
||
changes/<app>-<date>.diff unified diff against the release you edited
|
||
changes/<app>-<date>.files one "M|A|D path" line per changed file
|
||
|
||
When asked to merge, reapply that diff onto the new source by editing files
|
||
(do not rely on git being installed), keep the intent of each change where the
|
||
new release moved code, then ./makepad build <app> until it compiles. Report
|
||
any change that no longer applies.
|
||
|
||
## Rules
|
||
|
||
- Keep edits inside sources/. Built bundles (<App>.app) are regenerated.
|
||
- Your licenses do not allow publishing the commercial sources.
|
||
EOF
|
||
}
|
||
|
||
if [ -n "$command" ]; then
|
||
fetch_licenses
|
||
rust=local
|
||
case "$command" in
|
||
env) print_env ;;
|
||
agent-context) print_agent_context ;;
|
||
list) { printf '%s\n' "$licenses" | cut -d'|' -f1,2,5; printf '%s\n' "$free_apps" | sed 's#$#|sources/makepad/apps#'; } | awk -F'|' '{ printf "%-12s %-14s %s\n", $1, $2, $3 }' ;;
|
||
build|run)
|
||
app=${1:?usage: makepad $command APP}
|
||
# Plain, line-based output for agents: every compiler message,
|
||
# then one result line they can check.
|
||
printf 'Compiling %s with Rust %s (preview: simulated)\n' "$app" "$rust_pin"
|
||
printf 'Bundled %s/%s.app\n' "$root" "$app"
|
||
[ "$command" = build ] || printf 'Opened %s.app\n' "$app" ;;
|
||
esac
|
||
exit 0
|
||
fi
|
||
|
||
# Coding agents: only what is installed on this machine.
|
||
agents=
|
||
for a in 'claude|Claude Code' 'codex|Codex' 'grok|Grok'; do
|
||
if command -v "${a%%|*}" >/dev/null 2>&1; then agents="$agents${agents:+
|
||
}$a"; fi
|
||
done
|
||
|
||
# --------------------------------------------------------------- terminal ---
|
||
exec 3<>/dev/tty || { printf 'Makepad needs an interactive terminal.\n' >&2; exit 1; }
|
||
e=$(printf '\033')
|
||
dim="$e[2m" b="$e[1m" ok="$e[32m" acc="$e[36m" warn="$e[33m" inv="$e[7m" r0="$e[0m"
|
||
tty_saved=$(stty -g <&3)
|
||
leave() {
|
||
stty "$tty_saved" <&3 2>/dev/null || :
|
||
printf '%s[?25h%s[?1049l' "$e" "$e"
|
||
}
|
||
trap leave EXIT
|
||
trap 'exit 130' INT TERM HUP
|
||
printf '%s[?1049h%s[?25l' "$e" "$e"
|
||
stty -icanon -echo min 1 time 0 <&3
|
||
|
||
short() { printf '%s' "$1"; }
|
||
at() { printf '%s[%d;1H%s[2K' "$e" "$1" "$e"; }
|
||
line() { printf '%s%s[K\n' "$*" "$e"; y=$((y + 1)); }
|
||
size() {
|
||
width=$(tput cols 2>/dev/null || printf 80)
|
||
height=$(tput lines 2>/dev/null || printf 24)
|
||
[ "$width" -le 80 ] || width=80
|
||
}
|
||
rule() { r=; i=4; while [ "$i" -lt "$width" ]; do r="${r}─"; i=$((i + 1)); done; printf ' %s%s%s' "$dim" "$r" "$r0"; }
|
||
|
||
key() {
|
||
# The trailing x keeps a newline (Return) distinguishable from end of
|
||
# input; a closed terminal quits instead of pressing Return forever.
|
||
k=$(dd bs=1 count=1 <&3 2>/dev/null; printf x)
|
||
k=${k%x}
|
||
case "$k" in
|
||
'') exit 1 ;;
|
||
"$e")
|
||
stty min 0 time 1 <&3
|
||
s=$(dd bs=2 count=1 <&3 2>/dev/null)
|
||
stty min 1 time 0 <&3
|
||
case "$s" in '[A'|OA) key=up ;; '[B'|OB) key=down ;; '[C'|OC) key=right ;; '[D'|OD) key=left ;; '') key=esc ;; *) key=other ;; esac ;;
|
||
'
|
||
'|' ') key=enter ;;
|
||
k) key=up ;;
|
||
j) key=down ;;
|
||
*) key=$k ;;
|
||
esac
|
||
}
|
||
|
||
# ------------------------------------------------------------------ rows ---
|
||
# A screen is a list of rows: "head|TITLE", "note|TEXT" or
|
||
# "item|id|name|license|status|action".
|
||
state_cells() { # state_cells ID -> st (status text) and act (action)
|
||
case "$(get "$1")" in
|
||
new) st="${dim}download 42 MB, compile and run$r0" act== ;;
|
||
partial) st="${warn}download stopped at 38% · resume, compile and run$r0" act== ;;
|
||
compile) st="${warn}compile and run$r0" act== ;;
|
||
ready) st="${ok}✓$r0 ready" act=run ;;
|
||
merge) st="${warn}updated · your changes to merge$r0" act='merge with agent' ;;
|
||
esac
|
||
}
|
||
license_rows() {
|
||
printf '%s\n' "$licenses" | while IFS='|' read -r id title lic mb _; do
|
||
state_cells "$id"
|
||
[ "$(get "$id")" != new ] || st="${dim}download $mb MB, compile and run$r0"
|
||
printf 'item|%s|%s|%s|%s|%s\n' "$id" "$title" "$lic" "$st" "$act"
|
||
done
|
||
}
|
||
free_rows() {
|
||
# One shared download covers every app here, so rows stay short: a mark
|
||
# when built, and the action only on the selected row.
|
||
if [ "$(get makepad)" = new ]; then
|
||
printf '%s\n' "note|" "note|${dim}One 42 MB download covers them all; each compiles on first run.$r0"
|
||
else
|
||
printf '%s\n' "note|" "note|${dim}Each one compiles the first time you run it.$r0"
|
||
fi
|
||
printf '%s\n' "$free_apps" | while IFS='|' read -r id title; do
|
||
case "$(get "$id")" in
|
||
ready) st="${ok}✓$r0 ready" act=run ;;
|
||
new) st= act='download source, compile and run' ;;
|
||
*) st= act='compile and run' ;;
|
||
esac
|
||
printf 'item|%s|%s||%s|%s\n' "$id" "$title" "$st" "$act"
|
||
done
|
||
}
|
||
agent_rows() {
|
||
if [ -n "$agents" ]; then
|
||
printf '%s\n' "$agents" | while IFS='|' read -r id title; do
|
||
printf 'item|agent-%s|%s||%s|open\n' "$id" "$title" ""
|
||
done
|
||
fi
|
||
printf 'item|agent-shell|Shell||%s|open\n' "${dim}with this folder's Rust on PATH$r0"
|
||
}
|
||
# Disk: what this folder uses. The live version sums `du -sk` of target/
|
||
# (build data), sources/ + changes/ (source), toolchain/ (Rust) and the
|
||
# built <App>.app bundles (apps); the preview derives the split from the app
|
||
# states. Clearing build data deletes target/ only: the apps keep running,
|
||
# the next compile starts from scratch.
|
||
disk_row() {
|
||
kb_build=0 kb_src=0 kb_rust=0 kb_apps=0
|
||
case "$rust" in none|installing) ;; *) kb_rust=2600000 ;; esac
|
||
for id in scope amp makepad; do
|
||
case "$(get "$id")" in new) ;; *) kb_src=$((kb_src + 240000)) ;; esac
|
||
done
|
||
for id in scope amp $(printf '%s\n' "$free_apps" | cut -d'|' -f1); do
|
||
if [ "$(get "$id")" = ready ]; then
|
||
kb_apps=$((kb_apps + 30000))
|
||
[ "${cleared:-}" = yes ] || kb_build=$((kb_build + 380000))
|
||
fi
|
||
done
|
||
total=$((kb_build + kb_src + kb_rust + kb_apps))
|
||
free_kb=$(df -k "$HOME" | awk 'NR == 2 { print $4 }')
|
||
n() { awk -v k="$1" 'BEGIN { printf "%.1f", k / 1048576 }'; }
|
||
# What the folder uses, and the part a clean build can delete (target/).
|
||
# Sources, the toolchain and built apps stay.
|
||
if [ "$kb_build" -eq 0 ]; then
|
||
printf 'item|disk|Disk||%s|\n' "$(n "$total") GB used"
|
||
return
|
||
fi
|
||
printf 'item|disk|Disk||%s|%s\n' "$(n "$total") GB used ${dim}· build $(n "$kb_build") GB$r0" 'clear build data'
|
||
}
|
||
|
||
clear_build() {
|
||
[ "${cleared:-}" != yes ] || return 0
|
||
choose "Delete the build data in $(short "$root")/target? Your apps keep working; the next compile starts from scratch." keep delete keep || return 0
|
||
[ "$chosen" = delete ] || return 0
|
||
spin 'Deleting build data' 0.6
|
||
cleared=yes
|
||
message="${ok}✓$r0 Build data deleted. Built apps are unchanged."
|
||
}
|
||
|
||
# The email lives only in this folder (makepad.json): the download carries
|
||
# none, and a reinstall into the same folder remembers it.
|
||
account_row() {
|
||
if [ -n "$email" ]; then printf '%s' "item|account|Account||${ok}✓$r0 $email|switch email"
|
||
else printf '%s' "item|account|Account||${warn}not logged in$r0|log in"; fi
|
||
}
|
||
# Put the selection on the row a step is working on, so the marker follows
|
||
# the work (installing…) instead of staying where the person last was.
|
||
select_id() {
|
||
n=$(main_rows | grep -v '^$' | grep '^item|' | grep -n "^item|$1|" | cut -d: -f1)
|
||
[ -z "$n" ] || sel=$((n - 1))
|
||
}
|
||
|
||
main_rows() {
|
||
case "$rust" in
|
||
none) rs="${warn}not installed$r0" ra=install ;;
|
||
installing) rs="${acc}installing…$r0" ra= ;;
|
||
*) rs="${ok}✓$r0 Visual Studio Build Tools, Windows SDK, Rust $rust_pin" ra=recheck ;;
|
||
esac
|
||
if [ "$nvidia" != yes ]; then cs="${dim}needs an NVIDIA GPU$r0" ca=;
|
||
elif [ "$cuda" = installing ]; then cs="${acc}installing…$r0" ca=;
|
||
elif [ "$cuda" = ready ]; then cs="${ok}✓$r0 CUDA $cuda_pin ${dim}· AI features in Makepad Amp$r0" ca=;
|
||
else cs="${dim}optional · AI features in Makepad Amp$r0" ca=install; fi
|
||
ready=0 total=0
|
||
for id in $(printf '%s\n' "$free_apps" | cut -d'|' -f1); do
|
||
total=$((total + 1))
|
||
[ "$(get "$id")" != ready ] || ready=$((ready + 1))
|
||
done
|
||
printf '%s\n' "head|SETUP" \
|
||
"$(account_row)" \
|
||
"item|gpu|Graphics||$(if [ "$gpu" = ok ]; then printf '%s' "${ok}✓$r0 driver notice read"; else printf '%s' "${warn}read the driver notice$r0"; fi)|$([ "$gpu" = ok ] || printf read)" \
|
||
"item|terms|Agreements||$(if [ "$accepted" = yes ]; then printf '%s' "${ok}✓$r0 accepted ${dim}· Makepad, Microsoft, Rust$([ "$cuda" != ready ] || printf ', NVIDIA')$r0"; else printf '%s' "${dim}Makepad, Microsoft, Rust, NVIDIA$r0"; fi)|read" \
|
||
"item|rust|Build tools||$rs|$ra" \
|
||
"item|cuda|CUDA||$cs|$ca" \
|
||
"$(disk_row)" \
|
||
"$(if [ -z "$checked" ]; then printf '%s' "item|updates|Update||${dim}check for updates$r0|="; else printf '%s' "item|updates|Update||$checked|check again"; fi)" \
|
||
"head|YOUR APPS"
|
||
case "$licensed" in
|
||
unknown) printf '%s\n' "note|${dim}checking licenses for ${email}…$r0" ;;
|
||
*) if [ -z "$email" ]; then printf '%s\n' "item|login|Log in with your email address|||to see your licenses"; elif [ -n "$licenses" ]; then license_rows; else printf '%s\n' "note|${dim}none on this email yet · buy or request beta access at makepad.nl$r0"; fi ;;
|
||
esac
|
||
printf '%s\n' "head|MAKEPAD EXPERIMENTS" \
|
||
"item|free|Experiments|free|$total · $ready ready|open list" \
|
||
"head|CODING AGENTS · each one knows how to change and rebuild these apps"
|
||
agent_rows
|
||
}
|
||
|
||
# ---------------------------------------------------------------- screen ---
|
||
draw() {
|
||
size
|
||
case "$screen" in
|
||
main) rows_out=$(main_rows | grep -v '^$'); crumb= sub='Makepad apps ship as source, so your own coding agent can customize them.' ;;
|
||
free) rows_out=$(free_rows); crumb=' › Makepad experiments' sub='From our open source repository: experiments, not finished applications.' ;;
|
||
terms) rows_out=$(terms_rows); crumb=' › License agreements' sub='Return opens an agreement in your browser.' ;;
|
||
consent) rows_out=$(consent_rows); crumb=" › $consent_title" sub='Please read what you are agreeing to.' ;;
|
||
work) rows_out=$(work_rows); crumb=" › $work_title" sub=$work_sub ;;
|
||
signin) rows_out=$(signin_rows); crumb=' › Log in' sub='Welcome to the Makepad Builder.' ;;
|
||
gpu) rows_out=$(gpu_rows); crumb=' › Graphics' sub='Before you continue.' ;;
|
||
esac
|
||
items=$(printf '%s\n' "$rows_out" | grep -c '^item|' || :)
|
||
y=0
|
||
printf '%s[H' "$e"
|
||
line
|
||
left="Makepad commercial apps$crumb"
|
||
shown_email=${email:-not logged in}
|
||
pad=$((width - 4 - ${#left} - ${#shown_email}))
|
||
[ "$pad" -gt 1 ] || pad=1
|
||
line " ${b}Makepad$r0 commercial apps$crumb$(printf '%*s' "$pad" '')$dim${email:-not logged in}$r0"
|
||
line " $dim$sub$r0"
|
||
# Long lists scroll: keep the selection inside the visible window.
|
||
room=$((height - 11))
|
||
[ "$room" -ge 3 ] || room=3
|
||
[ "$sel" -ge "$top" ] || top=$sel
|
||
[ "$sel" -lt $((top + room)) ] || top=$((sel - room + 1))
|
||
[ "$screen" != main ] || top=0
|
||
i=0 shown=0
|
||
while IFS='|' read -r kind id title lic st act; do
|
||
case "$kind" in
|
||
head) line; line " $dim$id$r0"; continue ;;
|
||
step)
|
||
case "$id" in
|
||
cur) work_row=$((y + 1)); line " ${acc}●$r0 $title" ;;
|
||
done) line " ${ok}✓$r0 $title" ;;
|
||
*) line " ${dim}○ $title$r0" ;;
|
||
esac
|
||
continue ;;
|
||
note) line " $id"; continue ;;
|
||
esac
|
||
if [ "$i" -lt "$top" ] || { [ "$screen" != main ] && [ "$shown" -ge "$room" ]; }; then i=$((i + 1)); continue; fi
|
||
[ "$shown" -gt 0 ] || [ "$screen" = main ] || [ "$screen" = consent ] || [ "$screen" = gpu ] || [ "$screen" = signin ] || line
|
||
# Pad plain text; escape codes must not count toward column widths.
|
||
name=$(printf '%-15s' "$title")
|
||
case "$lic" in
|
||
commercial) lt="$b$(printf '%-20s' 'commercial license')$r0" ;;
|
||
beta) lt="$warn$(printf '%-20s' 'beta access')$r0" ;;
|
||
free) lt="$dim$(printf '%-20s' 'free, open source')$r0" ;;
|
||
*) lt= ;;
|
||
esac
|
||
if [ "$i" = "$sel" ]; then
|
||
# act "=" means the status already says what Return does.
|
||
hint=; [ -z "$act" ] || hint=" $acc$act ⏎$r0"; [ "$act" != = ] || hint=" ${acc}⏎$r0"
|
||
# With no status the action sits in the status column itself.
|
||
[ -n "$st" ] || hint="${hint# }"
|
||
line " ${acc}›$r0 $b$name$r0 $lt$st$hint"
|
||
else
|
||
line " $name $lt$st"
|
||
fi
|
||
i=$((i + 1)) shown=$((shown + 1))
|
||
done <<ROWS
|
||
$rows_out
|
||
ROWS
|
||
if [ "$screen" != main ]; then
|
||
more=$((items - top - shown))
|
||
if [ "$more" -gt 0 ]; then line " ${dim}↓ $more more$r0"; else line; fi
|
||
fi
|
||
line
|
||
line "$(rule)"
|
||
status_row=$((y + 1))
|
||
[ "$screen" != work ] || [ -z "${work_row:-}" ] || status_row=$work_row
|
||
line " $message"
|
||
line
|
||
footer_row=$((y + 1))
|
||
if [ "$screen" = work ]; then
|
||
line " ${dim}working · ctrl+c stops$r0"
|
||
elif [ "$screen" = signin ]; then
|
||
line " ${dim}type your email ⏎ continue$r0"
|
||
elif [ "$screen" = consent ] || [ "$screen" = gpu ]; then
|
||
line " ${dim}↑↓ move ⏎ select esc cancel$r0"
|
||
elif [ "$screen" = main ]; then
|
||
line " ${dim}↑↓ move ⏎ select q quit$r0"
|
||
else
|
||
line " ${dim}↑↓ move ⏎ select esc back q quit$r0"
|
||
fi
|
||
printf '%s[J' "$e"
|
||
}
|
||
|
||
status() {
|
||
at "$status_row"
|
||
# On a work page the status line is the current step's own line.
|
||
if [ "$screen" = work ]; then printf ' %s●%s %s' "$acc" "$r0" "$*"; else printf ' %s' "$*"; fi
|
||
}
|
||
|
||
# While work runs, the key hints give way to a busy line; the next draw
|
||
# puts them back.
|
||
busy() { at "${footer_row:-$((status_row + 2))}"; printf ' %s' "${dim}working · ctrl+c stops$r0"; }
|
||
|
||
bar() { # bar LABEL FROM% TOTAL UNIT SECONDS TEXT
|
||
busy
|
||
label=$1 from=$2 total=$3 unit=$4 secs=$5 text=$6 steps=40
|
||
i=$((from * steps / 100))
|
||
while [ "$i" -le "$steps" ]; do
|
||
fill=$((i * 30 / steps)) s= t= k=0
|
||
while [ "$k" -lt 30 ]; do if [ "$k" -lt "$fill" ]; then s="${s}━"; else t="${t}─"; fi; k=$((k + 1)); done
|
||
n=$(awk -v t="$total" -v i="$i" -v n="$steps" 'BEGIN { v = t * i / n; if (t >= 100) printf "%d", v; else printf "%.1f", v }')
|
||
status "$(printf '%-12s' "$label") $acc$s$dim$t$r0 $(printf '%3d%%' $((i * 100 / steps))) $dim$n / $total $unit · $text$r0"
|
||
sleep "$(awk -v s="$secs" -v n="$steps" 'BEGIN { printf "%.3f", s / n }')"
|
||
i=$((i + 1))
|
||
done
|
||
}
|
||
|
||
spin() { # spin TEXT SECONDS
|
||
busy
|
||
i=0 n=$(awk -v s="$2" 'BEGIN { printf "%d", s * 12 }')
|
||
while [ "$i" -lt "$n" ]; do
|
||
case $((i % 10)) in 0) c=⠋ ;; 1) c=⠙ ;; 2) c=⠹ ;; 3) c=⠸ ;; 4) c=⠼ ;; 5) c=⠴ ;; 6) c=⠦ ;; 7) c=⠧ ;; 8) c=⠇ ;; *) c=⠏ ;; esac
|
||
status "$acc$c$r0 $1"
|
||
sleep 0.08
|
||
i=$((i + 1))
|
||
done
|
||
}
|
||
|
||
# choose QUESTION DEFAULT OPTION...: ←→ or a first letter picks, ⏎ accepts,
|
||
# $note, when set, is a dim remark after the options (cleared on return).
|
||
# Escape backs out.
|
||
choose() {
|
||
q=$1 pick=$2; shift 2
|
||
while :; do
|
||
o=
|
||
for opt in "$@"; do
|
||
if [ "$opt" = "$pick" ]; then o="$o $inv $opt $r0"; else o="$o $opt "; fi
|
||
done
|
||
status "$q"
|
||
at $((status_row + 1)); printf ' %s %s' "$o" "$dim${note:-}$r0"
|
||
key
|
||
case "$key" in
|
||
enter) chosen=$pick; at $((status_row + 1)); note=; return 0 ;;
|
||
esc|q) chosen=; at $((status_row + 1)); note=; return 1 ;;
|
||
left|right)
|
||
prev= next= found=
|
||
for opt in "$@"; do
|
||
if [ -n "$found" ] && [ -z "$next" ]; then next=$opt; fi
|
||
[ "$opt" != "$pick" ] || found=1
|
||
[ -n "$found" ] || prev=$opt
|
||
done
|
||
if [ "$key" = left ] && [ -n "$prev" ]; then pick=$prev; fi
|
||
if [ "$key" = right ] && [ -n "$next" ]; then pick=$next; fi ;;
|
||
*) for opt in "$@"; do case "$(printf '%s' "$opt" | tr '[:upper:]' '[:lower:]')" in "$key"*) chosen=$opt; at $((status_row + 1)); note=; return 0 ;; esac; done ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# edit PROMPT: a normal line editor on the status line; empty keeps nothing.
|
||
edit() {
|
||
status "$1 "
|
||
stty "$tty_saved" <&3
|
||
printf '%s[?25h' "$e"
|
||
IFS= read -r edited <&3 || edited=
|
||
printf '%s[?25l' "$e"
|
||
stty -icanon -echo min 1 time 0 <&3
|
||
}
|
||
|
||
# ---------------------------------------------------------------- actions ---
|
||
# First screen of a first run: the email that holds the licenses. It is kept
|
||
# in this folder only (makepad.json), so a rerun or reinstall here skips this.
|
||
signin_rows() {
|
||
printf '%s\n' "note|" \
|
||
"note|Enter the email address you bought a Makepad app with, such as Scope" \
|
||
"note|or Amp, or that has beta access. It shows your licenses and stays in" \
|
||
"note|this folder only." \
|
||
"note|" \
|
||
"note|${dim}Leave it empty to continue with the open source experiments; you can" \
|
||
"note|${dim}log in later from the menu.$r0"
|
||
}
|
||
signin() {
|
||
s_screen=$screen s_sel=$sel
|
||
screen=signin
|
||
while :; do
|
||
draw
|
||
edit "Email:"
|
||
case "$edited" in
|
||
'') break ;;
|
||
*@*.*) email=$edited; break ;;
|
||
*) message="${warn}That does not look like an email address.$r0" ;;
|
||
esac
|
||
done
|
||
message= screen=$s_screen sel=$s_sel
|
||
}
|
||
|
||
switch_account() {
|
||
if [ -n "$email" ]; then edit "Email for your licenses ${dim}(⏎ keeps $email)$r0:"; else edit "Email you bought a Makepad app with:"; fi
|
||
case "$edited" in
|
||
'') return ;;
|
||
*@*.*) ;;
|
||
*) message="${warn}That does not look like an email address.$r0"; return ;;
|
||
esac
|
||
email=$edited
|
||
refresh
|
||
n=0; [ -z "$licenses" ] || n=$(printf '%s\n' "$licenses" | wc -l | tr -d ' ')
|
||
message="${ok}✓$r0 Switched to $email · $n licenses."
|
||
}
|
||
|
||
# License agreements. Installing the build tools accepts the Microsoft and
|
||
# Rust ones, installing CUDA the NVIDIA one; Return on a row opens it.
|
||
makepad_license_url=https://makepad.nl/commercial-license
|
||
agreements="makepad|Makepad commercial license|$makepad_license_url
|
||
vs|Microsoft Visual Studio Build Tools|https://visualstudio.microsoft.com/license-terms/vs2022-ga-diagnosticbuildtools/
|
||
sdk|Microsoft Windows SDK|https://learn.microsoft.com/legal/windows-sdk/windows-sdk-license
|
||
rust|Rust|https://www.rust-lang.org/policies/licenses
|
||
cuda|NVIDIA CUDA Toolkit|https://docs.nvidia.com/cuda/eula/"
|
||
accepted=no
|
||
|
||
terms_rows() {
|
||
printf '%s\n' "$agreements" | while IFS='|' read -r id title url; do
|
||
# Opened from a consent screen, the page lists only its agreements.
|
||
case " ${terms_only:-$id} " in *" $id "*) ;; *) continue ;; esac
|
||
host=${url#https://}; host=${host%%/*}
|
||
printf 'item|%s|%-36s||%s|open in browser\n' "$id" "$title" "$dim$host$r0"
|
||
done
|
||
}
|
||
|
||
# The agreements screen, entered from the SETUP row or from the acceptance
|
||
# question; Escape returns to wherever it was opened from.
|
||
terms_loop() {
|
||
t_screen=$screen t_sel=$sel t_top=$top t_message=$message
|
||
screen=terms sel=0 top=0 message=
|
||
while :; do
|
||
draw
|
||
key
|
||
message=
|
||
case "$key" in
|
||
up) sel=$(( (sel + items - 1) % items )) ;;
|
||
down) sel=$(( (sel + 1) % items )) ;;
|
||
esc|q) break ;;
|
||
enter)
|
||
select_row
|
||
url=$(printf '%s\n' "$agreements" | awk -F'|' -v id="$id" '$1 == id { print $3 }')
|
||
open "$url" 2>/dev/null || :
|
||
message="${ok}✓$r0 Opened ${url#https://} in your browser." ;;
|
||
esac
|
||
done
|
||
screen=$t_screen sel=$t_sel top=$t_top message=$t_message
|
||
}
|
||
|
||
# consent TITLE INTRO DETAIL ID...: a screen of its own for the license terms
|
||
# a step needs. The agreements are listed by name, Return opens one in the
|
||
# browser; "Agree to all" (selected at the start) and Cancel close the list.
|
||
consent() {
|
||
c_screen=$screen c_sel=$sel c_message=$message
|
||
consent_title=$1 consent_intro=$2 consent_detail=$3; shift 3
|
||
consent_ids="$*"
|
||
screen=consent message=
|
||
# Start on "Agree to all", below the agreements.
|
||
sel=$(($# + 0))
|
||
while :; do
|
||
draw
|
||
key
|
||
message=
|
||
case "$key" in
|
||
up) sel=$(( (sel + items - 1) % items )) ;;
|
||
down) sel=$(( (sel + 1) % items )) ;;
|
||
esc|q) id=cancel ;;
|
||
enter) select_row ;;
|
||
*) continue ;;
|
||
esac
|
||
case "$key/$id" in
|
||
*/agree) screen=$c_screen sel=$c_sel message=$c_message; draw; return 0 ;;
|
||
*/cancel) screen=$c_screen sel=$c_sel message=$c_message; return 1 ;;
|
||
enter/*)
|
||
url=$(printf '%s\n' "$agreements" | awk -F'|' -v id="$id" '$1 == id { print $3 }')
|
||
open "$url" 2>/dev/null || :
|
||
message="${ok}✓$r0 Opened ${url#https://} in your browser." ;;
|
||
esac
|
||
done
|
||
}
|
||
consent_rows() {
|
||
printf '%s\n' "note|" "note|$consent_intro" "note|$dim$consent_detail$r0" "head|READ THE AGREEMENTS"
|
||
printf '%s\n' "$agreements" | while IFS='|' read -r id title url; do
|
||
case " $consent_ids " in *" $id "*) ;; *) continue ;; esac
|
||
host=${url#https://}; host=${host%%/*}
|
||
printf 'item|%s|%-36s||%s|open in browser\n' "$id" "$title" "$dim$host$r0"
|
||
done
|
||
printf '%s\n' "note|" "item|agree|Agree to all|||$consent_what" "item|cancel|Cancel|||"
|
||
}
|
||
|
||
# The GPU notice is a setup step with a screen of its own, like the license
|
||
# terms; its answer is kept.
|
||
setup_gpu() {
|
||
g_screen=$screen g_sel=$sel g_message=$message
|
||
screen=gpu sel=0 message=
|
||
while :; do
|
||
draw
|
||
key
|
||
case "$key" in
|
||
up) sel=0 ;;
|
||
down) sel=1 ;;
|
||
enter) [ "$sel" -eq 0 ] && gpu=ok; break ;;
|
||
esc|q) sel=1; break ;;
|
||
esac
|
||
done
|
||
r_sel=$sel
|
||
screen=$g_screen sel=$g_sel message=$g_message
|
||
[ "$r_sel" -eq 0 ]
|
||
}
|
||
gpu_rows() {
|
||
printf '%s\n' "note|" \
|
||
"note|Makepad heavily relies on your GPU to draw its UI and implement AI" \
|
||
"note|functionality. Old hardware and broken drivers can cause your computer" \
|
||
"note|to reboot unexpectedly." \
|
||
"note|" \
|
||
"item|continue|I understand|||" \
|
||
"item|cancel|Cancel|||"
|
||
}
|
||
|
||
# One question covers every agreement; accept is the default.
|
||
accept_terms() {
|
||
[ "$accepted" != yes ] || return 0
|
||
while :; do
|
||
choose "Makepad apps come with the Makepad commercial license and Rust's licenses." accept accept read quit || return 1
|
||
case "$chosen" in
|
||
accept) accepted=yes; return 0 ;;
|
||
read) terms_loop; draw ;;
|
||
quit) return 1 ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# A page of its own while something runs: the steps as a checklist, the
|
||
# current one carrying its progress bar, then back to the menu with the result.
|
||
# work_begin TITLE SUBTITLE STEP... work_step STEP work_end MESSAGE
|
||
work_begin() {
|
||
w_screen=$screen w_sel=$sel
|
||
work_title=$1 work_sub=$2; shift 2
|
||
work_steps=$(printf '%s\n' "$@")
|
||
work_cur= screen=work message=
|
||
}
|
||
work_step() { work_cur=$1; draw; }
|
||
work_end() { screen=$w_screen sel=$w_sel message=$1; }
|
||
work_rows() {
|
||
printf '%s\n' "note|"
|
||
seen=no
|
||
printf '%s\n' "$work_steps" | while IFS= read -r label; do
|
||
if [ "$label" = "$work_cur" ]; then state=cur; seen=yes
|
||
elif [ "$seen" = yes ] || [ -z "$work_cur" ]; then state=next
|
||
else state=done; fi
|
||
printf 'step|%s|%s\n' "$state" "$label"
|
||
done
|
||
}
|
||
|
||
setup_rust() {
|
||
consent_what='install the build tools and Rust'
|
||
consent 'Install build tools' "Makepad compiles from source, so it needs a compiler (1.6 GB download)." "Installed in this folder only: Microsoft C++ Build Tools, Windows SDK, Rust $rust_pin" makepad vs sdk rust || return 0
|
||
work_begin 'Install build tools' 'This takes a few minutes; everything goes into this folder.' 'Build tools' 'Windows SDK' 'Rust'
|
||
work_step 'Build tools'; bar 'Build tools' 0 770 MB 1.5 'Microsoft C++ Build Tools'
|
||
work_step 'Windows SDK'; bar 'Windows SDK' 0 710 MB 1.5 'download and unpack'
|
||
work_step 'Rust'; bar Rust 0 66.4 MB 1 static.rust-lang.org
|
||
rust=local accepted=yes
|
||
work_end "${ok}✓$r0 Build tools and Rust $rust_pin installed in this folder. Nothing else on your PC changed."
|
||
select_id rust
|
||
}
|
||
|
||
setup_cuda() {
|
||
if [ "$nvidia" != yes ]; then
|
||
message="${dim}CUDA needs an NVIDIA graphics card; none was found.$r0"
|
||
return
|
||
fi
|
||
if [ "$cuda" = ready ]; then
|
||
message="${ok}✓$r0 CUDA $cuda_pin is installed; Makepad Amp builds with its AI features."
|
||
return
|
||
fi
|
||
consent_what='install CUDA'
|
||
consent 'Install CUDA' "CUDA adds the AI features in Makepad Amp (2.9 GB download)." "Installed in this folder only: NVIDIA CUDA Toolkit $cuda_pin" cuda || return 0
|
||
work_begin 'Install CUDA' 'About 3 GB, into this folder only.' CUDA
|
||
work_step CUDA; bar CUDA 0 2940 MB 3 developer.download.nvidia.com
|
||
cuda=ready
|
||
[ "$(get amp)" != ready ] || put amp compile
|
||
work_end "${ok}✓$r0 CUDA $cuda_pin installed. Makepad Amp recompiles with its AI features on its next run."
|
||
select_id cuda
|
||
}
|
||
|
||
# Download what is missing, compile when the build is older than the source,
|
||
# bundle, then run: every app goes through the same steps from whatever state
|
||
# it is in. The result is a real <Title>.app (icon, Info.plist, the binary
|
||
# inside), opened with `open`, so its Dock icon can be kept.
|
||
open_app() { # open_app ID TITLE MB CRATES
|
||
id=$1 title=$2 mb=$3 crates=$4
|
||
if [ "$rust" = none ]; then setup_rust; [ "$rust" != none ] || return 0; fi
|
||
state=$(get "$id")
|
||
if [ "$state" != ready ]; then
|
||
case "$state" in
|
||
new | partial) work_begin "$title" 'Download the source, compile it, then run it.' 'Download source' Compile Open ;;
|
||
*) work_begin "$title" 'Compile the source, then run it.' Compile Open ;;
|
||
esac
|
||
case "$state" in
|
||
new) work_step 'Download source'; bar 'Download source' 0 "$mb" MB 2.5 makepad.nl ;;
|
||
partial) work_step 'Download source'; bar 'Download source' 38 "$mb" MB 1.8 resuming ;;
|
||
esac
|
||
work_step Compile; bar Compile 0 "$crates" crates 3 compiling
|
||
put "$id" ready
|
||
work_step Open; spin "Opening $title" 0.5
|
||
w_open=yes
|
||
else
|
||
spin "Opening $title" 0.5
|
||
w_open=no
|
||
fi
|
||
message="${ok}✓$r0 $title is running. ${dim}Right-click its taskbar icon › Pin to taskbar.$r0"
|
||
[ "$w_open" != yes ] || work_end "$message"
|
||
}
|
||
|
||
open_free() { # a free app: the shared Makepad download first, then its build
|
||
id=$1 title=$2
|
||
if [ "$(get makepad)" = new ]; then
|
||
if [ "$rust" = none ]; then setup_rust; [ "$rust" != none ] || return 0; fi
|
||
bar Makepad 0 41.7 MB 2 'open source apps, shared by all of them'
|
||
put makepad ready
|
||
for f in $(printf '%s\n' "$free_apps" | cut -d'|' -f1); do [ "$(get "$f")" != new ] || put "$f" compile; done
|
||
fi
|
||
open_app "$id" "$title" 0 96
|
||
}
|
||
|
||
open_agent() { # open_agent TITLE [TASK]
|
||
if [ "$rust" = none ]; then setup_rust; [ "$rust" != none ] || return 0; fi
|
||
# The live version writes AGENTS.md + CLAUDE.md from print_agent_context,
|
||
# leaves this screen, runs the agent in the folder with print_env applied
|
||
# (TASK as its first prompt), and redraws when the agent exits.
|
||
spin "Writing AGENTS.md for $1" 0.4
|
||
message="${ok}✓$r0 $1 would open in $(short "$root") knowing ./makepad build and run. ${dim}Preview: sh makepad-mac.sh agent-context$r0"
|
||
[ -z "${2:-}" ] || message="${ok}✓$r0 $1 would open with: ${dim}$2$r0"
|
||
}
|
||
|
||
pick_agent() { # sets agent to the one installed agent, or asks which
|
||
agent=
|
||
n=$(printf '%s\n' "$agents" | grep -c . || :)
|
||
case "$n" in
|
||
0) message="${warn}Install Claude Code, Codex or Grok to merge your changes, or apply the diff yourself.$r0"; return 1 ;;
|
||
1) agent=$(printf '%s\n' "$agents" | cut -d'|' -f2); return 0 ;;
|
||
esac
|
||
set -- $(printf '%s\n' "$agents" | cut -d'|' -f1)
|
||
choose "Merge your changes with which agent?" "$1" "$@" || return 1
|
||
agent=$(printf '%s\n' "$agents" | awk -F'|' -v id="$chosen" '$1 == id { print $2 }')
|
||
}
|
||
|
||
# After an update, local edits are never merged by the installer. Your edited
|
||
# tree was diffed against the release it came from (git on macOS/Linux,
|
||
# makepad-git in the Windows Builder, so no git is needed there), the diff was
|
||
# saved in changes/, and the app now holds the clean new release. Your coding
|
||
# agent reapplies the diff; it reads it like any file.
|
||
merge_changes() { # merge_changes ID TITLE
|
||
pick_agent || return 0
|
||
diff="changes/$1-$(date +%Y-%m-%d).diff"
|
||
open_agent "$agent" "Reapply my changes in $diff onto the new $2 source, then ./makepad build $1."
|
||
put "$1" compile
|
||
}
|
||
|
||
# Updates: licenses first (new purchases appear, expired ones go), then every
|
||
# downloaded repository is compared with the newest release on makepad.nl.
|
||
# An untouched repository is replaced by a clean fetch of the new release. A
|
||
# repository you changed is first saved as a diff against its release:
|
||
# changes/<app>-<date>.diff unified diff, new files included
|
||
# changes/<app>-<date>.files one "M|A|D path" line per changed file
|
||
# then replaced the same way, and the app waits for your agent to merge.
|
||
check_updates() {
|
||
refresh
|
||
got= kept=
|
||
if [ "$demo" = installed ] && [ "$(get amp)" = ready ]; then
|
||
spin 'Makepad Amp has local changes: saving them as a diff' 0.6
|
||
bar 'Makepad Amp' 0 188 MB 2 'clean source for the new release'
|
||
put amp merge
|
||
got='Makepad Amp' kept=' Your 3 changed files are saved in changes/; select Makepad Amp to merge them.'
|
||
fi
|
||
if [ "$demo" = installed ] && [ "$(get makepad)" = ready ]; then
|
||
bar Makepad 0 41.7 MB 1.5 'clean source for the new release'
|
||
put makepad updated
|
||
for f in $(printf '%s\n' "$free_apps" | cut -d'|' -f1); do [ "$(get "$f")" != ready ] || put "$f" compile; done
|
||
got="$got${got:+ and }Makepad apps"
|
||
fi
|
||
checked="${ok}✓$r0 up to date · $(date +%H:%M)"
|
||
if [ -n "$got" ]; then
|
||
checked="updates downloaded · $(date +%H:%M)"
|
||
message="${ok}✓$r0 Updated $got.$kept"
|
||
else
|
||
message="${ok}✓$r0 Licenses checked; everything is up to date."
|
||
fi
|
||
}
|
||
|
||
refresh() {
|
||
if [ -z "$email" ]; then licenses= licensed=yes; return; fi
|
||
licensed=unknown
|
||
draw
|
||
spin "Checking licenses for $email" 0.8
|
||
fetch_licenses
|
||
licensed=yes
|
||
}
|
||
|
||
select_row() {
|
||
row=$(printf '%s\n' "$rows_out" | grep '^item|' | sed -n "$((sel + 1))p")
|
||
IFS='|' read -r _ id title _ _ _ <<EOF
|
||
$row
|
||
EOF
|
||
}
|
||
|
||
# ------------------------------------------------------------------- run ---
|
||
scan
|
||
screen=main sel=0 top=0
|
||
# The email comes first; everything after it can show the licenses.
|
||
[ "$demo" != first ] || [ -n "$email" ] || signin
|
||
# First run walks the setup steps in order: the driver notice, then the build
|
||
# tools. Each is also a SETUP row to revisit.
|
||
# Screens follow each other directly; the menu first appears once the
|
||
# person has agreed (or cancelled), never in between.
|
||
if [ "$demo" = first ]; then
|
||
gpu=
|
||
setup_gpu || exit 0
|
||
setup_rust
|
||
else
|
||
gpu=ok accepted=yes
|
||
fi
|
||
refresh
|
||
[ "$(get scope)" != partial ] || message="${warn}An earlier run stopped while downloading Makepad Scope. Select it to resume.$r0"
|
||
draw
|
||
[ "$rust" != none ] || { setup_rust; draw; }
|
||
# Start on the first licensed app.
|
||
# Start on the first licensed app, or on logging in.
|
||
if [ -z "$email" ] || [ -n "$licenses" ]; then sel=$(main_rows | sed '/^head|YOUR APPS/q' | grep -c '^item|'); fi
|
||
|
||
while :; do
|
||
draw
|
||
key
|
||
message=
|
||
case "$key" in
|
||
up) sel=$(( (sel + items - 1) % items )) ;;
|
||
down) sel=$(( (sel + 1) % items )) ;;
|
||
q) exit 0 ;;
|
||
esc) if [ "$screen" = main ]; then exit 0; else screen=main sel=$back top=0; fi ;;
|
||
enter)
|
||
select_row
|
||
case "$screen/$id" in
|
||
main/account) switch_account ;;
|
||
main/login) switch_account ;;
|
||
main/rust) if [ "$rust" = none ]; then setup_rust; else spin 'Checking the build tools' 0.5; message="${ok}✓$r0 Build tools, Windows SDK and Rust are ready."; fi ;;
|
||
main/cuda) setup_cuda ;;
|
||
main/gpu) setup_gpu || : ;;
|
||
main/updates) check_updates ;;
|
||
main/disk) clear_build ;;
|
||
main/terms) terms_loop ;;
|
||
main/free) back=$sel screen=free sel=0 top=0 ;;
|
||
main/agent-*) open_agent "$title" ;;
|
||
main/*)
|
||
if [ "$(get "$id")" = merge ]; then merge_changes "$id" "$title"; continue; fi
|
||
mb=$(printf '%s\n' "$licenses" | awk -F'|' -v id="$id" '$1 == id { print $4 }')
|
||
open_app "$id" "$title" "$mb" 214 ;;
|
||
free/*) open_free "$id" "$title" ;;
|
||
esac ;;
|
||
esac
|
||
done
|