···27272828key decisions
2929-------------
3030- 2025-10-25: chose dioxus desktop (standalone)
3030+ 2025-10-25: chose iced
3131 why:
3232- - native rendering (not webview/DOM)
3333- - react-like syntax (familiar)
3434- - no tauri IPC boundary issues
3535- - handles large datasets well
3636- - windows support + accessibility
3232+ - TRUE native rendering (no DOM/webview)
3333+ - elm architecture (clean patterns)
3434+ - handles 60k items natively
3535+ - system76 backing (cosmic desktop)
3636+ - actual GPU performance
37373838 rejected:
3939- - tauri: IPC boundary loses type safety
4040- - egui: immediate mode, less familiar
4141- - iced: accessibility gaps
4242- - yew: older, heavier
3939+ - tauri: IPC boundary loses type safety, still DOM
4040+ - dioxus: discovered it uses webview/DOM anyway
4141+ - egui: immediate mode, friend suggested iced instead
4242+ - yew: older, heavier, still web tech
4343+4444+ tradeoffs accepted:
4545+ - accessibility gaps (screen reader/IME)
4646+ - we're okay with this for now
434744484549architecture notes
4650------------------
4751 tech stack:
4848- - dioxus desktop (native rendering)
5252+ - iced (native GPU rendering)
4953 - rust for data handling
5054 - radio4000 API for data source
51555252- getting started:
5353- cargo install dioxus-cli --locked
5454-5555- optional (if we need tauri later):
5656- cargo install tauri-cli --version '^2.0.0' --locked
5656+ structure:
5757+ /r4 → dioxus prototype (abandoned)
5858+ TBD → new iced project
575958605961 .---.
···7779- changelog format → ~/Notes/changelogs.md
788079818080-last updated: 2025-10-25 (chose dioxus, defined project goal)
8282+last updated: 2025-10-25 (chose iced after exploring options)
+232
docs/iced.txt
···11+iced reference
22+==============
33+44+source: https://docs.iced.rs/iced/
55+66+77+the elm architecture
88+--------------------
99+1010+iced follows the elm architecture with four core parts:
1111+1212+ state → the data (struct with fields)
1313+ messages → interactions (enum variants)
1414+ update → how messages change state (fn)
1515+ view → how state dictates widgets (fn)
1616+1717+flow:
1818+ widgets produce messages
1919+ messages trigger update
2020+ update changes state
2121+ state dictates new view
2222+ repeat...
2323+2424+2525+basic structure
2626+---------------
2727+2828+pub fn main() -> iced::Result {
2929+ iced::run(update, view)
3030+}
3131+3232+fn update(state: &mut State, message: Message) {
3333+ match message {
3434+ Message::Increment => state.value += 1,
3535+ }
3636+}
3737+3838+fn view(state: &State) -> Element<'_, Message> {
3939+ button(text(state.value))
4040+ .on_press(Message::Increment)
4141+ .into()
4242+}
4343+4444+#[derive(Debug, Clone)]
4545+enum Message {
4646+ Increment,
4747+}
4848+4949+5050+widgets
5151+-------
5252+5353+widgets are VALUES not side effects
5454+created with helper functions from iced::widget
5555+5656+ button("+") → button widget
5757+ text("hello") → text widget
5858+ column![a, b, c] → vertical layout
5959+ row![a, b, c] → horizontal layout
6060+ container(widget) → positioning/alignment
6161+6262+configured with builder pattern:
6363+ button("+")
6464+ .on_press(Message::Inc)
6565+ .style(button::primary)
6666+ .width(Fill)
6767+6868+6969+layout
7070+------
7171+7272+no unified layout system
7373+each widget has its own strategy
7474+7575+common patterns:
7676+ - column for vertical stacking
7777+ - row for horizontal stacking
7878+ - container for positioning/centering
7979+ - spacing() for gaps between children
8080+8181+ container(
8282+ column![
8383+ "Top",
8484+ row!["Left", "Right"].spacing(10),
8585+ "Bottom"
8686+ ].spacing(10)
8787+ )
8888+ .padding(10)
8989+ .center_x(Fill)
9090+ .center_y(Fill)
9191+9292+9393+sizing
9494+------
9595+9696+Length enum controls widget dimensions:
9797+9898+ Fill → take all available space
9999+ Shrink → use intrinsic size (default)
100100+ Pixels(n) → fixed size
101101+102102+ container("text")
103103+ .height(300)
104104+ .width(Fill)
105105+106106+107107+styling
108108+-------
109109+110110+each widget has .style() method
111111+takes closure: (theme, status) -> style
112112+113113+ button("Click")
114114+ .style(|theme: &Theme, status| {
115115+ match status {
116116+ button::Status::Active => {
117117+ button::Style::default()
118118+ .with_background(theme.palette().primary)
119119+ }
120120+ _ => button::primary(theme, status)
121121+ }
122122+ })
123123+124124+convenience functions:
125125+ - container::rounded_box
126126+ - button::primary
127127+ - text::danger
128128+129129+130130+theming
131131+-------
132132+133133+use application builder for custom theme:
134134+135135+ iced::application(new, update, view)
136136+ .theme(theme)
137137+ .run()
138138+139139+ fn theme(state: &State) -> Theme {
140140+ Theme::TokyoNight
141141+ }
142142+143143+built-in themes:
144144+ - Theme::Dark
145145+ - Theme::Light
146146+ - Theme::TokyoNight
147147+ - etc
148148+149149+150150+tasks (async work)
151151+------------------
152152+153153+update can return Task<Message>
154154+used for async operations
155155+156156+ fn update(state: &mut State, msg: Message) -> Task<Message> {
157157+ match msg {
158158+ Message::Fetch => {
159159+ Task::perform(fetch_data(), Message::Fetched)
160160+ }
161161+ Message::Fetched(data) => {
162162+ state.data = Some(data);
163163+ Task::none()
164164+ }
165165+ }
166166+ }
167167+168168+tasks can:
169169+ - run futures/streams
170170+ - interact with runtime (window, focus, etc)
171171+ - be mapped, chained, batched, canceled
172172+173173+174174+subscriptions (passive data)
175175+-----------------------------
176176+177177+subscribe to external events:
178178+ - time ticks
179179+ - window resize
180180+ - keyboard events
181181+182182+ fn subscription(state: &State) -> Subscription<Message> {
183183+ window::resize_events()
184184+ .map(|(_, size)| Message::Resized(size))
185185+ }
186186+187187+subscriptions are declarative
188188+only what's returned is active (like view)
189189+190190+191191+scaling apps
192192+------------
193193+194194+update/view/message compose nicely
195195+common pattern: split into screens
196196+197197+ enum Screen {
198198+ Contacts(Contacts),
199199+ Chat(Chat),
200200+ }
201201+202202+ enum Message {
203203+ Contacts(contacts::Message),
204204+ Chat(chat::Message),
205205+ }
206206+207207+each screen has own update/view
208208+parent coordinates via Action enum
209209+use .map() to compose messages
210210+211211+ screen.view().map(Message::Contacts)
212212+213213+214214+key principles
215215+--------------
216216+217217+- widgets are values, no side effects
218218+- state is application-specific
219219+- type system prevents invalid states
220220+- messages are pure data (derive Debug, Clone)
221221+- view is pure function of state
222222+- leverages rust: ownership, traits, enums, etc
223223+224224+225225+important types
226226+---------------
227227+228228+Element<'a, Message> → generic widget
229229+Task<Message> → async work
230230+Subscription<Message> → passive events
231231+Length → Fill, Shrink, Pixels(n)
232232+Theme → appearance
+74
docs/workflow.txt
···11+workflow
22+========
33+44+principles
55+----------
66+- docs are for agents to rebuild context quickly
77+- AGENTS.md is the entry point for new sessions
88+- todo.txt tracks current work
99+- CHANGELOG.md tracks what's done
1010+- jj for version control (see ~/Notes/jj.md)
1111+1212+1313+onboarding new context
1414+----------------------
1515+when starting fresh:
1616+ 1. read AGENTS.md (or CLAUDE.md symlink)
1717+ 2. read todo.txt to see current tasks
1818+ 3. check relevant docs/ files as needed
1919+ 4. check CHANGELOG.md for recent history
2020+2121+2222+docs structure
2323+--------------
2424+ AGENTS.md → main context file for AI agents
2525+ CLAUDE.md → symlink to AGENTS.md
2626+ README.md → project overview
2727+ todo.txt → current work, backlog
2828+ CHANGELOG.md → completed work history
2929+3030+ docs/
3131+ README.md → docs folder overview
3232+ iced.txt → iced framework reference
3333+ workflow.txt → this file
3434+3535+3636+working with jj
3737+---------------
3838+use freely:
3939+ jj describe -m "message" → describe current change
4040+ jj new → start new change
4141+4242+see ~/Notes/jj.md for full reference
4343+4444+4545+completing tasks
4646+----------------
4747+1. do the work
4848+2. update CHANGELOG.md with what changed
4949+3. check off in todo.txt or move to backlog
5050+4. jj describe + jj new
5151+5. update AGENTS.md if decisions made
5252+5353+5454+context files to read first
5555+---------------------------
5656+always start with:
5757+ - AGENTS.md (decisions, status, references)
5858+ - todo.txt (what's next)
5959+6060+read as needed:
6161+ - docs/iced.txt (framework reference)
6262+ - docs/workflow.txt (this file)
6363+ - CHANGELOG.md (history)
6464+6565+6666+keeping context fresh
6767+---------------------
6868+update AGENTS.md when:
6969+ - making architecture decisions
7070+ - learning something important
7171+ - changing approach
7272+7373+keep it concise, use our ascii style
7474+future you (or other agent) will thank you