From 4e83ebbf506cc9b121d49b741b12458e39e11d13 Mon Sep 17 00:00:00 2001
From: ml
Date: Fri, 17 Jul 2026 20:48:31 +0200
Subject: [PATCH] Add daily notification wakeups and a `make watchapp`
packaging target
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Schedules a one-shot Pebble wakeup for the configured notification
time, re-arming it on every launch and config change, and vibrates/
refreshes the report when it fires (foreground or fresh launch).
Also adds a `make watchapp` target that builds the watchapp via the
Pebble SDK and copies a versioned .pbw to dist/, matching `make
package`'s version resolution — deliberately not wired into CI, which
has no Pebble SDK installed.
---
...n-is-being-continued-from-a-previous-c.txt | 4457 +++++++++++++++++
CLAUDE.md | 19 +
Makefile | 36 +-
README.md | 16 +
watch/.lock-waf_linux_build | 10 +
watch/src/c/main.c | 22 +
watch/src/c/notify.c | 33 +
watch/src/c/notify.h | 37 +
8 files changed, 4629 insertions(+), 1 deletion(-)
create mode 100644 2026-07-17-060832-this-session-is-being-continued-from-a-previous-c.txt
create mode 100644 watch/.lock-waf_linux_build
create mode 100644 watch/src/c/notify.c
create mode 100644 watch/src/c/notify.h
diff --git a/2026-07-17-060832-this-session-is-being-continued-from-a-previous-c.txt b/2026-07-17-060832-this-session-is-being-continued-from-a-previous-c.txt
new file mode 100644
index 0000000..b16e508
--- /dev/null
+++ b/2026-07-17-060832-this-session-is-being-continued-from-a-previous-c.txt
@@ -0,0 +1,4457 @@
+ ▐▛███▜▌ Claude Code v2.1.211
+▝▜█████▛▘ Sonnet 5 · Claude Pro
+ ▘▘ ▝▝ ~/krotik/deck_in_a_dash
+
+ ▎ Extended through July 19
+ ▎ We're extending Claude Fable 5 access on all paid plans, as well as keeping Claude Code’s weekly rate limits 50% higher, through July 19.
+ ▎
+ ▎ As before, you can use up to half of your weekly usage limit on Fable 5. After that, you can keep using Fable 5 with usage credits, or switch to another model to keep working within your remaining limits.
+ ▎
+ ▎ More details here: https://support.claude.com/en/articles/15424964-claude-fable-5-promotional-access
+
+❯ /compact
+ ⎿ Compacted (ctrl+o to see full summary)
+ ⎿ Read watch/package.json (165 lines)
+ ⎿ Read .gitignore (20 lines)
+ ⎿ Read watch/wscript (100 lines)
+ ⎿ Read watch/scripts/gen_app_icon.py (50 lines)
+ ⎿ Plan file referenced (~/.claude/plans/woolly-fluttering-naur.md)
+
+❯ see the screenshot here: [Image #1]
+ ⎿ [Image #1]
+
+● I can see the "Deck in a Dash" tile in the Apps list is blank while
+ Pebblemon/Checklist show real preview images. Let me inspect how the Pebble
+ tooling embeds icon/screenshot data in the built .pbw to understand what that
+ tile actually pulls from.
+
+ Searched for 4 patterns, read 1 file, ran 1 shell command (ctrl+o to expand)
+
+● Web Search("Pebble mobile app "My Apps" tile blank sideloaded app screenshot
+ missing icon Rebble store")
+ ⎿ Did 1 search in 8s
+
+● Web Search("Pebble app store "screenshots" required app submission pbw
+ appstore-info screenshot images")
+ ⎿ Did 1 search in 7s
+
+● That blank tile isn't a resource bug — it's expected for how the Pebble app is
+ being used. Comparing the two working tiles against ours:
+
+ - Pebblemon and Checklist both show real in-app screenshots. Those are apps
+ published on the Rebble app store, and the Pebble Developer Portal's
+ "Publishing an App" flow requires uploading up to 5 screenshots there when
+ creating the store listing — the screenshot lives on Rebble's servers, not
+ inside the .pbw.
+ - Deck in a Dash is only ever pebble installed directly (sideloaded) — it has
+ no Rebble store listing, so there's no screenshot for the phone app to fetch
+ and show. The tile falls back to blank instead of any icon.
+
+ I confirmed this by unzipping the built watch.pbw and checking
+ inject_metadata.py/generate_appinfo.py: the only icon-related field that ships
+ inside the app bundle is icon_resource_id (our 25x25 menuIcon), which is what
+ the watch's own launcher uses — already fixed and confirmed working. There's
+ no separate "screenshot" or "tile image" resource type in the package.json
+ schema for the phone's app list; that's store metadata, uploaded separately
+ through the Rebble Developer Portal at publish time.
+
+ So there's nothing to fix in the watch app's code or resources here — the
+ blank tile is just what a sideloaded, unpublished app looks like in that list.
+ The only way to make it show something is to actually publish a listing on
+ the Rebble app store (with screenshots uploaded there), which is a
+ distribution step outside this repo, not a code change. Given this is a
+ personal watchapp, I'd treat this as expected/cosmetic and not worth chasing
+ further unless you're planning to publish it.
+
+ Sources:
+ - Publishing an App - Pebble Developers - Rebble
+
+✻ Worked for 2m 13s
+
+※ recap: We're adding icons/logos to the Pebble watch app; watch launcher icon and phone config-page logo are both fixed and confirmed. The blank "My Apps" tile turned out to be expected (unpublished app, no Rebble store screenshot) — nothing left to fix there. (disable recaps in /config)
+
+❯ ok accepted. lets modify the interpreter further. the "Rat für heute" is at
+ the bottom currently it should be right after "bedeutung des Tages" the most
+ important transit should be repeated before the text.
+
+ Searched for 4 patterns, read 2 files (ctrl+o to expand)
+
+● Update(interpreter/src/main.c)
+ ⎿ Added 12 lines
+ 179 return buf;
+ 180 }
+ 181
+ 182 +/* Short "Transiting X Aspect natal Y" restatement of the day'
+ +s single
+ 183 + * most significant transit (no sign/house/orb/score - that de
+ +tail is
+ 184 + * still shown in full in the "Significant Transits" section f
+ +urther
+ 185 + * down), printed just above the guidance paragraph so the rea
+ +der has
+ 186 + * that context even though guidance.c's own intro sentence on
+ +ly names
+ 187 + * the planet, not the full aspect. */
+ 188 +static void print_top_transit_line(FILE *out, const Aspect *a)
+ + {
+ 189 + fprintf(out, "%s %s %s %s %s\n", ui("ui.transiting", "Transi
+ +ting"),
+ 190 + body_display_name(a->transiting_planet), aspect_disp
+ +lay_name(a->type),
+ 191 + ui("ui.natal", "natal"), body_display_name(a->natal_
+ +planet));
+ 192 +}
+ 193 +
+ 194 static void print_top_transits_header(FILE *out, int count) {
+ 195 if (count == 1) {
+ 196 fprintf(out, "%s\n", ui("interp.top_transits.one", "Top si
+ gnificant transit:"));
+
+● Now the text format reordering:
+
+● Update(interpreter/src/main.c) ⎿ Added 9 lines, removed 5 lines
+ 223 }
+ 224 fprintf(out, "\n");
+ 225
+ 226 + fprintf(out, "%s\n", ui("interp.heading_guidance", "Guidance
+ +"));
+ 227 + if (interp->top_item_count > 0) {
+ 228 + print_top_transit_line(out, &interp->top_items[0].aspect);
+ 229 + }
+ 230 + char guidance_buf[GUIDANCE_TEXT_MAX];
+ 231 + guidance_write(guidance_buf, sizeof guidance_buf, "", interp
+ +, &reading->spread);
+ 232 + fputs(guidance_buf, out);
+ 233 + fprintf(out, "\n");
+ 234 +
+ 235 if (interp->top_item_count == 0) {
+ 236 fprintf(out, "%s\n", ui("interp.no_notable_transits", "No
+ notable transits today."));
+ 237 } else {
+ ...
+ 258 fprintf(out, "\n");
+ 259
+ 260 print_celtic_cross_text(out, &reading->spread);
+ 252 - fprintf(out, "\n");
+ 253 -
+ 254 - char guidance_buf[GUIDANCE_TEXT_MAX];
+ 255 - guidance_write(guidance_buf, sizeof guidance_buf, "", interp
+ -, &reading->spread);
+ 256 - fputs(guidance_buf, out);
+ 261 }
+ 262
+ 263 /* ===== --format html ===== */
+
+● Now the HTML format:
+
+● Update(interpreter/src/main.c)
+ ⎿ Added 19 lines
+ 293 }
+ 294 fprintf(out, "
\n");
+ 295
+ 296 + char *guidance = capture_guidance(interp, &reading->spread);
+ 297 + fprintf(out, "%s
\n\n", ui("
+ +interp.heading_guidance", "Guidance"));
+ 298 + if (interp->top_item_count > 0) {
+ 299 + const Aspect *top_a = &interp->top_items[0].aspect;
+ 300 + fprintf(out, "
%s %s %s %s %s
\n", u
+ +i("ui.transiting", "Transiting"),
+ 301 + body_display_name(top_a->transiting_planet), aspec
+ +t_display_name(top_a->type),
+ 302 + ui("ui.natal", "natal"), body_display_name(top_a->
+ +natal_planet));
+ 303 + }
+ 304 + const char *start = guidance;
+ 305 + for (const char *p = guidance; ; p++) {
+ 306 + if (*p == '\n' || *p == '\0') {
+ 307 + if (p > start) fprintf(out, "
%.*s
\n", (int)(p - s
+ +tart), start);
+ 308 + if (*p == '\0') break;
+ 309 + start = p + 1;
+ 310 + }
+ 311 + }
+ 312 + free(guidance);
+ 313 + fprintf(out, "
\n");
+ 314 +
+ 315 fprintf(out, "%s
\n", ui("interp.heading_transits",
+ "Significant Transits"));
+ 316 if (interp->top_item_count == 0) {
+ 317 fprintf(out, "%s
\n", ui("interp.no_notable_transits
+ ", "No notable transits today."));
+
+● Now remove the old guidance block that used to sit at the end of the HTML
+ output:
+
+ Read 1 file (ctrl+o to expand)
+
+● Update(interpreter/src/main.c)
+ ⎿ Removed 13 lines
+ 352 }
+ 353 fprintf(out, "\n");
+ 354
+ 355 - char *guidance = capture_guidance(interp, &reading->spread);
+ 356 - fprintf(out, "%s
\n\n", ui("
+ -interp.heading_guidance", "Guidance"));
+ 357 - const char *start = guidance;
+ 358 - for (const char *p = guidance; ; p++) {
+ 359 - if (*p == '\n' || *p == '\0') {
+ 360 - if (p > start) fprintf(out, "
%.*s
\n", (int)(p - s
+ -tart), start);
+ 361 - if (*p == '\0') break;
+ 362 - start = p + 1;
+ 363 - }
+ 364 - }
+ 365 - free(guidance);
+ 366 - fprintf(out, "
\n");
+ 367 -
+ 355 fprintf(out, "