CLI & projects

The day command manages a project from creation through testing and packaging. The same commands work in a terminal, an editor, or CI. This reference covers the commands and the project settings they use.

The commands

day --help groups commands by task. Use day help <command> for its options and subcommands, for example day help icon new. A target flag accepts -p, --platform, or --target. icon, sign, and project require a subcommand; bare day doctor runs quick toolchain checks. day doctor verify builds and packages test apps and can take several minutes.

day new                      # interactive: scaffold an app, a piece, or a part
day new app my-app           # scaffold a new app non-interactively (--no-website to skip the site config)
day project add-target android-mdc   # add a target to an existing app
day localize list|add|remove # survey the project's locales, or add/remove one on every surface at once
day prepare                  # render the derived host files (icon catalogs, mipmaps) under build/day/host (--check: CI gate)
day open -p <target>         # prepare, then open the host project in Xcode / Android Studio / DevEco
day icon build              # render platform icons from the source icon
day icon new                # create a seeded source icon and render its platform icons
day icon check              # check for icon drift without writing files (exit 5)
day build   -p macos-appkit  # build one target
day launch  -p macos-gtk     # build + run on a target
day launch  --git <url>      # clone a repository and run the app in it — no checkout needed
day launch  --day-src <path|url>  # run this app against another day, for one build
day build   --flavor custom  # build the Day-custom.toml flavor of this app (/docs/flavors)
day pack    -p macos-appkit  # build + sign + produce a distributable artifact (.dmg here)
day sign check              # report release-signing readiness without printing secrets
day rebuild <artifact>       # rebuild a shipped artifact from its provenance and compare the bytes
day lint                     # check ids, Fluent coverage, project shape (--fix applies what it can)
day devices list             # simulators, emulators and phones a mobile target can launch onto
day devices boot -p ios-uikit <id>  # start a simulator/AVD so it can be launched onto
day doctor                   # check toolchains for every target
day doctor verify           # doctor, then scaffold + build + pack a throwaway app per target
day stop --all               # stop running launches (sessions in build/day/sessions.json)
day clean                    # remove all build artifacts (build/, target/, gradle/hvigor outputs); --dry-run lists them
day relaunch --all-running   # stop + rebuild + relaunch — "apply my changes"
day drive -p <t> --steps-json ''   # drive a running app with dayscript steps
day patch --local <checkout> # build against a local day (or piece) checkout; repeatable (--check: verify)
day patch --git <url>[@<ref>] # build against a fork of day, for the whole graph; commit the table
day mcp-server               # serve Day tools to AI agents (Model Context Protocol, stdio)
day version                  # print the CLI version, build profile, and git ref (always the commit)

--flavor <name> applies to every command, not only build: it layers Day-<name>.toml over Day.toml so one source tree ships as several apps. Build flavors covers the file and what each key changes.

day patch switches an app from the published git dependency to a local checkout of day, or of an external piece or part, or to a fork of day, and verifies the switch took; Developing Day and an app together covers when and how to use it.

day pack produces a standalone, installable package per target. See Packaging & distribution for formats, signing, and CI:

targetartifact
macos-appkit.dmg (codesign → notarize → staple)
ios-uikit.ipa (App Store export; without signing config, an unsigned device .ipa named <stem>-ios-uikit-unsigned.ipa)
android-mdc.apk + .aab (release-signed)
linux-gtk / linux-qtsingle-file .flatpak bundle and a .appimage
windows-xaml.msix + NSIS -setup.exe
harmony-arkui.hap
web-domnone — day build already emits a self-contained static dist/

Run day new with no arguments to be walked through choosing what to create (app / piece / part) and which platforms and toolkits to support. Every question has an equivalent flag, so the same choices can be made non-interactively, e.g. day new app my-app --toolkit ios-uikit --toolkit macos-appkit --appid com.example.myapp --title "My App". Scaffolds currently depend on day from its git remote (the framework crates are not yet published to crates.io); once they are, --registry pins them to your CLI’s version from crates.io and will become the default.

day new app scaffolds a working starter: a typed-route sidebar over four sample panels (a reactive counter, a controls tour, a canvas dial, and a drill-down stack), with locales, a dayscript walkthrough (day launch -p <target> --script dayscript/demo.yaml), and the native host projects the mobile targets build through. The scaffold comes from a template: a plain directory tree whose file contents and paths are rendered with mustache-style placeholders:

placeholderwhat it renders to
{{name}}the cargo package name, lowercase kebab
{{repo}}the name as typed, case intact — the scaffold directory and the Pages path
{{ident}}the crate’s Rust extern name (hyphens → underscores)
{{snake}} / {{pascal}}a snake_case stem and its PascalCase form
{{title}}the app’s display name
{{id}}the application id, reverse-DNS
{{org}}the id’s organization segment, which the generated website claims as its Pages host
{{scheme}}the deep-link scheme derived from the name
{{day_dep}} / {{day_build_dep}} / {{day_piece_deps}}dependency lines for the source the app was scaffolded against (git, crates.io, or a local checkout)
{{targets_toml}}the chosen targets, quoted, for Day.toml
{{targets_list}}the same targets bare, for a CI workflow’s targets: input
{{first_target}}the first chosen target, for the commands a README prints

The built-in template is embedded in the CLI (a fresh cargo install day-cli scaffolds offline); bring your own with:

day new app my-app --template ./my-template          # a local directory
day new app my-app --template https://github.com/you/tpl#v1   # a git repo (optional #ref)

day new piece also writes demo/ beside the crate: the same app template, cut to one page that shows the piece, with a dayscript/demo.yaml walkthrough. A native piece’s demo targets the platforms its toolkits draw on, and a composite piece’s demo targets all of them. The demo depends on the piece by path, so day launch -p <target> --script dayscript/demo.yaml, run from demo/, builds the code you are editing. --no-demo leaves it out.

day new --describe prints the questions themselves (every kind’s fields, their options, and the flag each one fills) as a versioned JSON document. It takes no project, so an editor can read it to build its own New Project dialog without copying the target list into a second place. The VS Code extension’s wizard is rendered entirely from it.

day new --describe | jq '.kinds[] | {id, fields: [.fields[].id]}'

Template conventions: a trailing .hbs on a filename is stripped after rendering (use Cargo.toml.hbs so tooling doesn’t mistake the template for a Rust package), _gitignore becomes .gitignore, _vscode/ and _github/ become .vscode/ and .github/, non-UTF-8 files (icons) copy verbatim, and an unknown {{placeholder}} is an error rather than silent empty output. Files under platform/<os>/ belong to that OS’s targets and are only scaffolded for targets that need them.

A template repository’s own .git, target/ and .github/ are skipped on load: the workflows under .github/ are that repository’s CI, and the ones a scaffolded app should get travel as _github/.

Add a platform later with day project add-target <target> (repeatable / comma-separated): it appends the target to Day.toml’s [app] targets array (via toml_edit, so your comments and formatting survive) and materializes the target’s native host project (platform/android/, platform/ios/, platform/ohos/) from the same template, never overwriting existing files. Pass the same --template the app was created with if it wasn’t the built-in one. day project migrate-xcode moves Xcode build settings into DayApp.xcconfig files without building the app. day build also performs this migration when needed.

Use day sign check to check signing configuration, or day sign status <id> to query a notarization submission. See signing configuration.

CI and editor integrations check for the commands they need. If a command is unavailable, update the CLI from the same Day checkout or Git revision as the integration. Metadata JSON and MCP tool names remain stable.

Running a repository directly

day launch --git <url> runs an app you haven’t checked out. It clones the repository, finds the Day project inside it, and launches that. With no -p, it builds for your host’s default toolkit, so trying a sample app is one command:

day launch --git https://github.com/daybrite/Day-Rise.git
day launch --git https://github.com/daybrite/Day-Rise.git@main       # a branch, tag, or commit
day launch --git https://github.com/daybrite/Day-Skies.git -p ios-uikit --env WEATHER_MOCK=1

The checkout is cached per URL and ref, under $XDG_CACHE_HOME/day/git/… when that’s set and your platform’s cache directory otherwise (~/Library/Caches/day on macOS, ~/.cache/day on Linux, %LOCALAPPDATA%\day\cache on Windows). Every run prints the path:

     Cloning daybrite/Day-Rise @ main
    Checkout ~/Library/Caches/day/git/github.com/daybrite/Day-Rise/main
  Defaulting macos-appkit (no --platform given)
    Building macos-appkit (xcodebuild Debug, macosx)
   Launching macos-appkit

That path is a working checkout, so cd there and start editing. The build tree lives inside it, which is why the second run is an incremental compile rather than a fresh one. A later run fetches and fast-forwards; once you’ve edited or committed in the checkout, it stops updating and builds what’s on disk, telling you so. Nothing is ever reset or force-updated. Cargo.lock is the one exception: building here is what rewrites it, so it doesn’t count as your edit, and it’s discarded when an incoming commit carries a new one. --dir <d> clones somewhere you name instead of the cache, and day stop --project <that path> ends a --detached run.

Each ref gets its own checkout, and a build tree runs to a couple of GB per target, so Day-Rise.git and Day-Rise.git@main cost twice what one of them does; pick a spelling and keep to it. day clean works on projects, not on this cache; to reclaim the space, delete the printed directory, or all of them at once:

rm -rf "${XDG_CACHE_HOME:-~/Library/Caches}/day/git"    # ~/.cache/day/git on Linux

For a repository holding more than one Day project, --project selects one by its path within the repo; without it, an ambiguous repository lists what it found.

--script works too, and a relative path that isn’t in your current directory is looked up in the checkout, so a repository’s own walkthrough runs by the name it has there:

day launch --git https://github.com/daybrite/Day-Showcase.git --script dayscript/walkthrough.yaml

--git builds and runs code from a URL. Pass URLs you trust, the same way you would with cargo install --git.

Trying another version of Day itself

--day-src points the app’s day dependencies somewhere else for one build. It takes a path to a day checkout, or a git URL with an optional @<ref> — a branch, a tag, a commit, or someone else’s fork. It’s on day build and day launch both, since answering “does this branch fix the bug?” means building with each version and looking at both:

day launch --day-src ../day                                               # a local checkout
day launch --day-src https://github.com/daybrite/day.git@experimental-nav  # a branch
day launch --day-src https://github.com/someone/day.git@fix-482            # a PR fork

day patch writes .cargo/config.toml and every later build uses it until you delete it; --day-src computes the same [patch] table, hands it to one cargo run, and leaves the project exactly as it found it — Cargo.lock included, which cargo rewrites during the build and which the CLI puts back afterwards. Use day patch when you’re developing the framework and the app together for a while, and --day-src when you want one look.

Each day-src gets its own build tree under build/day/day-src/<slug>/, so two versions can be compared without either one’s compile throwing away the other’s:

     Day src https://github.com/daybrite/day.git @ main
    Checkout ~/Library/Caches/day/git/github.com/daybrite/day/main
     Patched 33 day crate(s) → https://github.com/daybrite/day.git @ main
   Launching macos-appkit

Both apps can run at once, and in a debug build each window’s title says which framework it came from — Day Rise (0.1.0+main-2d77edbf/appkit) beside Day Rise (0.1.0+day-4aea8304/appkit). Switching back to a version you’ve already built is an incremental compile, not a fresh one.

On Android and HarmonyOS only the Rust half is isolated; Gradle and hvigor keep their own shared build directories, so the packaging step re-runs when you switch. And day pack takes no --day-src, so a shipped artifact always records the framework that built it.

day launch streams the app’s stdout/stderr back to your terminal and can drive it with a script:

# run a dayscript walkthrough after launch, capturing localized screenshots
day launch -p macos-gtk --script dayscript/walkthrough.yaml --locale fr

# capture variants of the same walkthrough: `--variant` names the screenshot subdirectory
# (build/day/screenshots/<target>/<variant>/) and DAY_THEME forces the theme on every backend
day launch -p macos-gtk --script dayscript/walkthrough.yaml --variant dark --env DAY_THEME=dark

# variant loops share one binary (theme and locale are runtime inputs): build once, then
# `--skip-build` reuses the artifact — on iOS this pays xcodebuild once instead of per variant
day build -p ios-uikit
day launch -p ios-uikit --skip-build --script dayscript/walkthrough.yaml --variant dark --env DAY_THEME=dark

# --record captures what you do into a replayable dayscript: drive the app by hand, and the file
# is rewritten continuously (see the dayscript "Recording" guide)
day launch -p macos-appkit --record recording.yaml

CI runs each showcase walkthrough once per theme × locale (light/dark × en/fr/ar/zh-CN) with one command: day launch --themes light,dark --locales en,fr,ar,zh-CN --script … builds once and expands the matrix internally, naming each run’s variant <theme> (for the default locale) or <theme>-<locale>. The gallery lets you flip every screenshot between those variants.

A scripted run captures the desktop toolkits and the web build at 2560×1600 pixels, a 1280×800-point window at 2×. --capture-size 2880x1800, the DAY_CAPTURE_SIZE variable, or a [screenshots] table in Day.toml changes it, and window captures at the app’s own [window] size (capture size).

Simulators, emulators, and devices

For HarmonyOS, run day devices boot -p harmony-arkui --headless to start the configured Oniro image without a window. It always waits for boot readiness, so --wait is accepted but unnecessary. ID, --device, --os, and --orientation are rejected for this target.

Without a device flag, a launch goes to every runtime of that kind it can see: every booted iOS simulator, every connected Android device and emulator. That suits a capture sweep. To target one phone, name it with a flag. Selection is one flag per runtime, so a single command can send each -p somewhere different:

FlagSelectsFind them with
--ios-device <name|udid>a physical iPhone or iPadxcrun devicectl list devices
--ios-simulator <name|udid>one booted simulatorxcrun simctl list devices booted
--android-device <serial>one device or emulatoradb devices
--ohos-device <key>one OpenHarmony device or emulatorhdc list targets

Or ask Day, which lists all three from any directory:

day devices list                      # every mobile target
day devices list -p ios-uikit         # just one
day --format json devices list        # for editors and scripts

# start something to launch onto: a simulator, an AVD, or the OpenHarmony emulator
day devices boot -p ios-uikit  C4C903E3-95E1-40F3-A3F8-45D3EAE035BB
day devices boot -p android-mdc Pixel_9_API_36

# and stop one when you are done with it
day devices shutdown -p ios-uikit  "iPhone 16 Pro"
day devices shutdown -p android-mdc Pixel_9_API_36

Booted simulators, attached phones, running emulators and reachable hdc targets come back under devices; simulators and AVDs that exist but are not running come back under bootable. Both halves name the flag that selects a device, so an editor can show a row for one before it has booted. A target whose toolchain is missing reports available: false with a note, instead of looking like nothing is plugged in.

day devices boot starts one of the bootable entries. On iOS an app cannot be installed onto a shut-down simulator, so boot one before day launch. With --wait, booting an Android emulator also turns off its “isn’t responding” and crash dialogs and its “Viewing full screen” hint, as day launch does on every emulator, so they stay out of screenshots. Physical devices keep their own settings.

--headless boots with no window, which is what a CI runner wants: it starts an Android emulator without one and keeps the iOS simulator’s UI app closed. Leave it off at a desk, where the boot opens that app so you can watch your app arrive.

Which app shows the simulator depends on the Xcode. Up to Xcode 26 it is Simulator.app, opened directly. Xcode 27 replaced it with Device Hub, which shows the one device it is told about, so Day opens it on the device being booted (open -a DeviceHub.app "devices://manage/select?id=<udid>"). Day picks whichever the selected Xcode ships, and day doctor names it as simulator-ui.

day launch -p ios-uikit brings that window up too when it is launching onto a single simulator, so an app started against an already-booted device is something you can watch. A run that targets several simulators at once (a capture sweep) opens none of them.

Device Hub shows nothing for a device that is still booting and does not correct itself afterwards, so a boot that is going to open a window waits for the device first. --headless skips both the wait and the window.

day devices shutdown is the other direction. Both spellings of an Android emulator work — the adb serial the listing reports, or the AVD name you booted it by — and the command waits until the emulator has gone, so the next listing describes the machine you are about to act on. Stopping something that is already stopped succeeds. Physical phones are refused; unplug one instead. The OpenHarmony emulator has no stop yet; close its window.

--android-device and --ohos-device take precedence over ANDROID_SERIAL and DAY_OHOS_TARGET, so an exported value keeps working as the default and the flag overrides it for one run. --device is an accepted alias for --ios-simulator.

Whichever device a run names is also the one its dayscript talks to and its screenshots come from; the port forward and the capture follow the selection rather than whichever device enumerated first.

# every booted simulator — the default, and what a screenshot sweep wants
day launch -p ios-uikit

# one booted simulator, by name or UDID
day launch -p ios-uikit --ios-simulator "iPhone 16 Pro"

# a physical iPhone
day launch -p ios-uikit --ios-device "iPhone 13 mini"

# one Android device or emulator, by adb serial
day launch -p android-mdc --android-device 19091FDF600BAY

# one OpenHarmony device or emulator, by hdc connect key
day launch -p harmony-arkui --ohos-device 127.0.0.1:55555

# both phones at once, from one command, with the logs interleaved
day launch -p ios-uikit    --ios-device "iPhone 13 mini" \
           -p android-mdc  --android-device 19091FDF600BAY

# start them and get the shell back rather than staying attached to the logs
day launch -p ios-uikit --ios-device "iPhone 13 mini" --detach

# drive a device run with a dayscript, the same as a simulator run
day launch -p ios-uikit --ios-device "iPhone 13 mini" --script dayscript/demo.yaml

# a phone and a desktop together, to compare the same screen side by side
day launch -p ios-uikit --ios-device "iPhone 13 mini" -p macos-appkit

Every target reports the same way. Day reports each step itself, and the tools underneath it (adb, devicectl, simctl) stay quiet unless they fail, at which point their output is the diagnostic. The two-phone command above prints:

     Signing Showcase.app (Day Showcase iOS Development)
  Installing ios-uikit on iPhone 13 mini
   Launching ios-uikit (dev.daybrite.showcase) on device iPhone 13 mini
  Installing android-mdc on 19091FDF600BAY
   Launching android-mdc (dev.daybrite.showcase) on 19091FDF600BAY (arm64-v8a)

and then streams both apps’ stdout and stderr, each line prefixed with the target it came from ([ios-uikit], [android-mdc]), so two phones running at once read apart. Ctrl-C stops the run and takes the log watchers with it.

What a physical iOS device needs

Naming --ios-device also changes the build: the iphoneos SDK instead of the simulator’s, and signing against a real identity, where a simulator build signs ad-hoc. Day signs the bundle after the build against a development provisioning profile installed for the app’s bundle id. The profile supplies both the signing identity (matched by fingerprint, so a machine holding several development certificates picks the right one) and the entitlements, so the signature cannot claim something its profile does not grant.

So the prerequisites are a paired device and a profile that covers this app and lists that device. Install one by double-clicking the .mobileprovision; without a match, the launch stops and says so rather than falling back to a simulator. Push is the case where the two halves have to agree: if Day.toml declares notifications, the build fails when the profile has no aps-environment, instead of installing an app that cannot register.

Pressing Run in Xcode needs one thing more, because Xcode signs during the build rather than after it: a development team. Set it in platform/ios/DayApp.local.xcconfig, which DayApp.xcconfig includes last and .gitignore already covers:

DEVELOPMENT_TEAM = ABCDE12345

That file belongs to your checkout, so a fork can sign with its own team, and with a bundle id its profile covers, while the committed project stays as it is.

Apple reports a locked device as RequestDenied; Day translates it:

[ios-uikit] the device is locked — unlock it and run again (iOS will not launch an app onto a locked screen)

Installing works on a locked phone; launching does not.

Checking the machine

day doctor reports what each toolkit needs and what’s missing. day doctor verify tests the answer by doing the work: it runs the doctor checks, then for every target this machine supports it scaffolds a throwaway app in a temporary directory, builds it, and packages it. Each target’s build time and packaged size are printed at the end.

day doctor verify                                   # every target this machine can build
day doctor verify -p ios-uikit,macos-appkit         # only these
day doctor verify --no-pack --profile release       # stop after the build; use the release profile
day doctor verify --day-version 0.2.0               # check that release, not the CLI you have

Run it with no arguments and a target whose prerequisites are missing is skipped, with the same fix line day doctor would print. Name targets with -p and a missing prerequisite is an error instead, since you said those targets work here. --strict fails the run on any target this machine could have checked but isn’t set up for (a target that only builds on another OS is never counted). The scheduled workflow in the day repository uses it to check each platform-toolkit pair against a freshly installed CLI. Under GitHub Actions the per-target table goes to the job summary.

The scaffolded projects are deleted at the end unless you pass --keep.

Checking a specific version of Day

--day-version picks which Day to verify. It sets both halves (the day CLI that scaffolds, builds, and packs, and the day your app depends on), so you never test one against the other:

day doctor verify --day-version main       # the main branch on GitHub
day doctor verify --day-version 0.2.0      # that release
day doctor verify --day-version latest     # the newest release on crates.io
day doctor verify --day-version a1b2c3d    # that commit

Unless the CLI you’re running is already the version you named, the command installs it into the run’s temporary directory (cargo install), so nothing on your PATH changes. The same spec goes to day new --day-version, which is available on its own if you only want to pin a project:

day new app my-app --day-version main       # day = { git = "…", branch = "main" }
day new app my-app --day-version 0.2.0      # day = { git = "…", tag = "v0.2.0" }

A release pins the matching vX.Y.Z git tag today, because the framework crates aren’t on crates.io yet; with --registry it pins the crates.io version instead.

The conventional project

A Day project is a normal Cargo package plus a small Day.toml: the project marker and the home of everything Day-specific. name and version are derived from Cargo.toml’s [package] and never restated, so identity can’t drift. Any [app] property can be overridden per platform, per toolkit, or per target ([app.ios], [app.qt], [app.macos-appkit]), with the most specific table winning. The build tool reads the resolved values when it derives platform metadata (an Android build’s label and applicationId, for example).

# Day.toml
schema = 1

[app]
id = "dev.daybrite.showcase"
title = "Day Showcase"
build = 1
targets = [
  "macos-appkit",
  "macos-gtk",
  "macos-qt",
  "ios-uikit",
  "android-mdc",
]

[window]
width = 480
height = 640

# Example: a different display title on iOS only.
[app.ios]
title = "Showcase Mobile"

day metadata prints the project’s identity, targets, and per-target resolved values; --json emits a versioned, machine-readable envelope (this is what the VS Code extension consumes instead of parsing Day.toml itself, and it also carries the full target catalog). day lint validates the manifest’s structure. Unknown targets and override tables that name no known platform/toolkit/target are findings.

Store listings

An app that ships to the App Store or Google Play keeps its listing under store/<locale>/, as plain text files named for what they are — name.txt, subtitle.txt, short.txt, description.txt, keywords.txt, release-notes.txt — one directory per locale, keyed the same way resource/locales/ is. day new app scaffolds it for any app with a mobile target, and day store init adds it to an existing one.

The two stores differ in field names, length limits (release notes: 4000 characters on the App Store, 500 on Google Play), which fields exist, and how a locale is spelled (zh-CN here is zh-Hans to Apple). day store stage resolves all of it, generating a ready-to-run fastlane project per target under build/day/store/<target>/, with validate and upload lanes. day pack runs the same generation, so a packaged build already has its listing beside it.

day lint checks the listing against the stores’ rules before an upload can reject it: length limits per store, required fields, URL format, leftover TODO placeholders, and locale parity with the app’s translations, so a new app locale also requires a listing in that locale. See Store listings for the full field table and the credential variables.

In CI, day lint --strict turns any finding into a failure (exit 10). A fresh scaffold trips one rule, because the listing text it ships is still TODO. Pass --allow store-placeholder to let that one code stand while every other rule still fails the run. An allowed code is still reported, as one line carrying its count and a sample, so an --allow nobody has revisited stays visible.

Linting

day lint reads the project’s sources, catalogs and manifest, and reports what it finds. Each finding carries a code you can --allow, and the file, line and column it is about:

error   day::lint::unknown-route     navigate: route "settings/theme" starts with "settings", which no `.item(…)` or `routes! { … }` declares (src/lib.rs:88)
warning day::lint::unused-key        resource/locales/en: history_hint is never referenced (resource/locales/en/app.ftl:434)

A finding is an error when it names something that does not exist, or that will misbehave once the app runs: a route nothing declares navigates nowhere, an undeclared permission terminates the app on iOS, an unknown target in Day.toml is not read. Coverage gaps and store copy are warnings. Both kinds fail --strict, so the split changes what you read rather than what CI does.

One rule that would pass that test stays a warning anyway. unknown-key (a tr("…") with no message, which renders the key itself on screen) is found by scanning for the literal after tr(", and that two-character name turns up inside other identifiers, where what follows it is not always a key.

Some findings come with a repair. day lint --fix applies them and reports each one:

$ day lint --fix
fixed   day::lint::store-whitespace     store/en/name.txt: Trim the surrounding whitespace
fixed   day::lint::store-bad-keywords   store/en/keywords.txt: Remove the spaces around commas

A rule proposes a fix only where there is one right answer and applying it cannot lose anything you wrote (trimming stray whitespace around a store field, dropping the spaces in a keyword list). Anything that would need a decision, or that would add text you did not write, reports and waits for you. A code you passed to --allow is never rewritten.

day lint --json emits a versioned envelope instead of the report (every finding with its place, its severity, and its fix); the VS Code extension draws its squiggles and quick fixes from it:

{
  "schema": 1,
  "findings": [
    {
      "code": "day::lint::store-whitespace",
      "severity": "warning",
      "message": "store/en/name.txt: leading or trailing whitespace",
      "waived": false,
      "file": "store/en/name.txt",
      "line": 1,
      "column": 1,
      "fix": {
        "title": "Trim the surrounding whitespace",
        "file": "store/en/name.txt",
        "contents": "Day Rise\n"
      }
    }
  ],
  "counts": { "errors": 0, "warnings": 1, "waived": 0, "fixable": 1 }
}

Waived findings appear too, marked "waived": true, so a tool can show them greyed instead of hiding an --allow that has outlived its reason.

Under GitHub Actions, findings also become annotations on the lines they name, plus a summary table on the run page.

One backend feature is enabled per binary; day launch -p <target> selects it, so the AppKit build contains only AppKit code and the Android build only its JNI bridge. The full directory anatomy, the per-target build pipelines, and how resources are packaged are covered in Project structure & builds.

dayscript

dayscript is a YAML language that drives and asserts a running app over a socket, using the same script on every platform. Pieces are addressed by the same stable .id you give them in Rust, and routes are the same keys your nav/nav_stack use. It has its own guide: Testing with dayscript.

Continuous integration

Every push builds the showcase on every target and runs the walkthrough, uploading each target’s screenshots (and its installable packages) as artifacts. This site’s gallery is assembled from those screenshot artifacts, so it always shows the latest captures from each platform that succeeded. Packaging & distribution covers the artifact pipeline, and Platform support reports what that CI shows, per target.