6. Finish and test the app

Finish the app with buttons that navigate between its two sections, then run an automated walkthrough. Start from 5. Navigate between pages. Keep Cargo.toml, Day.toml, build.rs, and src/main.rs from lesson 1. The complete UI and catalog below replace their earlier versions.

1. Complete the catalog

Replace resource/locales/en/app.ftl with this catalog. The earlier alert exercise’s unused messages are no longer needed:

app_title = Session counter
counter_value = Count: { $count }
add_one = Add one
reset_count = Reset
session_hint = Add one after each session. Reset when you start a new day.
nav_counter = Counter
nav_summary = Summary
summary_value = Sessions completed: { $count }
summary_hint = This count lasts until you quit the app.
show_summary = Show summary
back_to_counter = Back to counter

2. Complete the UI

Replace all of src/lib.rs:

use day::prelude::*;

day::resources!();
day::day_start!(options: res::window_options(), root);

pub fn root() -> impl Piece {
    let count = Signal::new(0i64);
    let section = Signal::new("counter".to_string());

    nav(section)
        .style(NavStyle::Sidebar)
        .title(res::str::app_title())
        .item("counter", res::str::nav_counter(), move || {
            counter_page(count)
        })
        .item("summary", res::str::nav_summary(), move || {
            summary_page(count)
        })
        .grow()
}

fn counter_page(count: Signal<i64>) -> impl Piece {
    column((
        label(res::str::counter_value(count)).id(id!(counter_label)),
        row((
            button(res::str::add_one())
                .action(move || count.update(|n| *n = n.saturating_add(1)))
                .id(id!(increment_button)),
            button(res::str::reset_count())
                .action(move || count.set(0))
                .id(id!(reset_button)),
        ))
        .spacing(Space::M),
        label(res::str::session_hint()),
        nav_link(res::str::show_summary(), "summary").id(id!(summary_button)),
    ))
    .spacing(Space::M)
    .padding(Space::L)
    .grow()
}

fn summary_page(count: Signal<i64>) -> impl Piece {
    column((
        label(res::str::summary_value(count)).id(id!(summary_label)),
        label(res::str::summary_hint()),
        nav_link(res::str::back_to_counter(), "counter").id(id!(counter_button)),
    ))
    .spacing(Space::M)
    .padding(Space::L)
    .grow()
}

nav_link makes a button that navigates to a route key. These links and the sidebar update the same navigation state. This app selects peer sections; it does not push a detail page onto a back stack. Every visible message comes from the generated catalog accessors.

3. Run the finished app

day launch -t macos-appkit

Add two sessions and press Show summary. Expect Sessions completed: 2. Press Back to counter, reset, and open the summary again; expect zero. Quit with Command-Q.

The app now combines event callbacks, reactive state, layout, and navigation. Press Add one after completing a session. The count stays in memory and resets when you quit.

4. Automate the walkthrough

Create a script directory:

mkdir -p dayscript

Create dayscript/session-counter.yaml:

name: session-counter
on_failure: stop
flow:
  - wait_for: { id: increment_button }
  - assert_text: { id: counter_label, key: counter_value, args: { count: 0 } }
  - tap: { id: increment_button }
  - tap: { id: increment_button }
  - assert_text: { id: counter_label, key: counter_value, args: { count: 2 } }
  - tap: { id: summary_button }
  - assert_route: { route: summary }
  - assert_text: { id: summary_label, key: summary_value, args: { count: 2 } }
  - tap: { id: counter_button }
  - assert_route: { route: counter }
  - assert_text: { id: counter_label, key: counter_value, args: { count: 2 } }
  - tap: { id: reset_button }
  - assert_text: { id: counter_label, key: counter_value, args: { count: 0 } }
  - tap: { id: summary_button }
  - assert_text: { id: summary_label, key: summary_value, args: { count: 0 } }

A dayscript is a YAML walkthrough executed by Day’s automation engine. IDs locate controls; message keys and arguments express expected localized text. assert_route checks the selected route. on_failure: stop stops this walkthrough at its first failure.

Run from the app directory:

day launch -t macos-appkit --locale en --script dayscript/session-counter.yaml

Expect all 15 steps to pass and the command to exit with status zero. Script assertion failures exit with status 5. Scripted taps exercise Day’s event dispatch inside the native app; also try the controls by hand because this is not a physical mouse or keyboard test.

Troubleshooting

SymptomCheck and recovery
A script cannot find an IDMatch the YAML identifier to id!(...), save both files, and rerun without --skip-build.
The first count is not zeroStart a fresh app run; the script assumes a new in-memory count.
Navigation does nothingMatch nav_link keys to the .item keys; a visible translated label is not a route key.
A text assertion failsCompare the expected argument with the actual count and keep the final Fluent catalog and Rust file together.

Next steps

You have completed the macOS learning path. Try the other-platform branch if you need another target. For a larger app, continue with localization, state, or navigation.