<feed xmlns='http://www.w3.org/2005/Atom'>
<title>kukuri.git/modules/orgflow-ui/src/wasmJsMain, branch main</title>
<subtitle>kukuri: Kotlin Multiplatform app (desktop / wasm / native / Android) for the orgflow UI</subtitle>
<link rel='alternate' type='text/html' href='https://openpgpkey.dangofactory.net/cgit/cgit.cgi/kukuri.git/'/>
<entry>
<title>Calendar: today-relative offsets, re-entering the month view, and a working pie layout</title>
<updated>2026-09-26T09:14:31+00:00</updated>
<author>
<name>charlie</name>
<email>root@dangofactory.local</email>
</author>
<published>2026-09-26T09:14:31+00:00</published>
<link rel='alternate' type='text/html' href='https://openpgpkey.dangofactory.net/cgit/cgit.cgi/kukuri.git/commit/?id=0dbaf11f991dbd9af39606a25cd6859ec0d8d69f'/>
<id>0dbaf11f991dbd9af39606a25cd6859ec0d8d69f</id>
<content type='text'>
+"-1"/"+1" is information about distance from today, not a step button. The row
now reads 今日から -21日 (or +N日) - how far the centre date is from today -
and pressing it returns to today. Showing it as a step control gave no sense of
distance, which was the point of the row.

The same row now exists on the month and year levels, in the unit that fits:
今月から +Nヶ月 and 今年から +N年. It is omitted when the value is zero, since
the centre already shows the current month or year and repeating it says
nothing.

Re-entering the month view. key(f) only re-keys when the frame changes, so
pressing Calendar while already in Calendar did nothing - the state survived.
A reentry counter is now part of the key, so pressing the same frame again
resets it to the default view. That fixes the case the previous fix missed.

The pie menu labels are finally placed correctly, after two wrong approaches
that are worth recording because the failure was invisible in the code:
  1. Computing offsets from the centre. Modifier.offset moves a node from
     where layout put it, so with a centre-aligned parent every label was
     displaced by half the parent's size. Only one of four appeared.
  2. A nested Box(fillMaxSize()) with per-slot Alignment. The inner box takes
     the window's size rather than the parent's, so labels were drawn far from
     the disc and nothing appeared near it.
Both failed for the same reason: the offset origin depends on the parent's
dimensions, which cannot be corrected with constants. The fix is to stop
computing positions and let layout do it - a Row of [label column, disc, label
column], each column a Column of two labels. The disc and its labels cannot
drift apart now because they are siblings in the same layout.

Month and year labels carried the same defect and are fixed by the same
change, since they all go through PieDial.

Also fixed a compile break of my own making, worth noting because it presented
as something unrelated: the file stopped compiling with "Unresolved reference"
on functions that plainly exist. The cause was a doubled /** - two doc-comment
openers in a row - which left one unterminated, so everything after it parsed
as comment. This is the second time a comment has caused a cascade of
unrelated errors in this file; Kotlin's nested comments mean an accidental /*
anywhere, including inside a comment, swallows the rest of the file.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
+"-1"/"+1" is information about distance from today, not a step button. The row
now reads 今日から -21日 (or +N日) - how far the centre date is from today -
and pressing it returns to today. Showing it as a step control gave no sense of
distance, which was the point of the row.

The same row now exists on the month and year levels, in the unit that fits:
今月から +Nヶ月 and 今年から +N年. It is omitted when the value is zero, since
the centre already shows the current month or year and repeating it says
nothing.

Re-entering the month view. key(f) only re-keys when the frame changes, so
pressing Calendar while already in Calendar did nothing - the state survived.
A reentry counter is now part of the key, so pressing the same frame again
resets it to the default view. That fixes the case the previous fix missed.

The pie menu labels are finally placed correctly, after two wrong approaches
that are worth recording because the failure was invisible in the code:
  1. Computing offsets from the centre. Modifier.offset moves a node from
     where layout put it, so with a centre-aligned parent every label was
     displaced by half the parent's size. Only one of four appeared.
  2. A nested Box(fillMaxSize()) with per-slot Alignment. The inner box takes
     the window's size rather than the parent's, so labels were drawn far from
     the disc and nothing appeared near it.
Both failed for the same reason: the offset origin depends on the parent's
dimensions, which cannot be corrected with constants. The fix is to stop
computing positions and let layout do it - a Row of [label column, disc, label
column], each column a Column of two labels. The disc and its labels cannot
drift apart now because they are siblings in the same layout.

Month and year labels carried the same defect and are fixed by the same
change, since they all go through PieDial.

Also fixed a compile break of my own making, worth noting because it presented
as something unrelated: the file stopped compiling with "Unresolved reference"
on functions that plainly exist. The cause was a doubled /** - two doc-comment
openers in a row - which left one unterminated, so everything after it parsed
as comment. This is the second time a comment has caused a cascade of
unrelated errors in this file; Kotlin's nested comments mean an accidental /*
anywhere, including inside a comment, swallows the rest of the file.
</pre>
</div>
</content>
</entry>
<entry>
<title>Calendar: fix the day-view header, month labels, year day taps, and the pie labels</title>
<updated>2026-09-26T08:24:47+00:00</updated>
<author>
<name>charlie</name>
<email>root@dangofactory.local</email>
</author>
<published>2026-09-26T08:24:47+00:00</published>
<link rel='alternate' type='text/html' href='https://openpgpkey.dangofactory.net/cgit/cgit.cgi/kukuri.git/commit/?id=f36f592dda9bab79db90727045efbf340941ca94'/>
<id>f36f592dda9bab79db90727045efbf340941ca94</id>
<content type='text'>
Seven items, one of which was a real bug with a clear cause.

Calendar would not return to the month view. Calling CalendarFrame() from a
when(f) means Compose keeps its remember blocks alive when you navigate away,
so leaving in the year or day view and coming back left you in that view.
Wrapping the frames in key(f) gives each frame its own state, so Calendar
always opens at the month view per 2.27.

The day view header is now the requested shape: the centre is the date being
viewed, written the same way the body used to write it (9月26日(土)), and the
row below carries the offset from today as pressable chips. Pressing them
moves. The offset is measured from today, not from the day being viewed - with
today+3 on screen the row reads [今日] [-1日] [+1日] and those go to today,
+2 and +4. Showing the current day again in that row would duplicate the
centre. The first version of this did get the arithmetic wrong, sending +N
back to the same day; caught and fixed while wiring it.

The same bar shape is used for all three levels: chevron, centred value,
chevron, and a pill on the month view. The month pill says 今月, not 今日 -
the day view is the one that says today.

Month event-bar labels were squashed: 9sp text in a 16dp bar left no vertical
padding at all. The bar is 20dp, the text 10sp with a 12sp line height, and
4dp of horizontal inset. Days with more than one event keep plain 5dp colour
bars - there is not enough height per bar for legible text, and colour alone
beats unreadable text.

Year view day taps now work. The previous version deliberately made days
non-tappable to keep the year level about picking a month; the request was to
jump to a day directly, so tapping a day in the year view opens that day.

Spacing between sections, grounded in a measurement rather than a convention.
Brumby &amp; Zhuang 2015 (CHI, DOI 10.1145/2702123.2702177 - open access, read in
full) measured 30px between items in a column and 250px between column
groups, about 1:8, and concluded that what actually governs search time is
whether the visual groups match the semantic ones - grouping unrelated items
visually slowed search down (M=3.34s semantically arranged vs M=4.23s random,
p&lt;.001). This app had 6dp between rows and 16dp between sections, only 1:2.7.
Sections are now 48dp for 1:8. The paper measured a ratio, not absolute
values, so only the ratio is borrowed and the absolute size follows the row
height here - which is more to stand on than 8dp-grid conventions: a survey
found that Gestalt proximity gives only a monotonic relation and that the
8dp grid, 44pt and 48dp+8dp figures are industry conventions with no
measurement study cited behind them.

Pie menu quadrant labels are now always visible, placed outside the circle at
1.15x the radius. They were previously only shown while held, to avoid the
occlusion that Callahan et al. 1988 (DOI 10.1145/57167.57182) recorded users
complaining about - but an always-present control with no labels gives no
reason to reach for it. Keeping them outside means the disc, its bands and
the centre W are still never covered.

Checked and NOT reproduced: black text in dark mode on Home, Timeline and
Note. The most recent captures of all three were read carefully and every
string is white or light grey. 2.31's spacing change is the only edit those
frames received in this pass. If specific text still reads as black, it needs
a screenshot that names it.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Seven items, one of which was a real bug with a clear cause.

Calendar would not return to the month view. Calling CalendarFrame() from a
when(f) means Compose keeps its remember blocks alive when you navigate away,
so leaving in the year or day view and coming back left you in that view.
Wrapping the frames in key(f) gives each frame its own state, so Calendar
always opens at the month view per 2.27.

The day view header is now the requested shape: the centre is the date being
viewed, written the same way the body used to write it (9月26日(土)), and the
row below carries the offset from today as pressable chips. Pressing them
moves. The offset is measured from today, not from the day being viewed - with
today+3 on screen the row reads [今日] [-1日] [+1日] and those go to today,
+2 and +4. Showing the current day again in that row would duplicate the
centre. The first version of this did get the arithmetic wrong, sending +N
back to the same day; caught and fixed while wiring it.

The same bar shape is used for all three levels: chevron, centred value,
chevron, and a pill on the month view. The month pill says 今月, not 今日 -
the day view is the one that says today.

Month event-bar labels were squashed: 9sp text in a 16dp bar left no vertical
padding at all. The bar is 20dp, the text 10sp with a 12sp line height, and
4dp of horizontal inset. Days with more than one event keep plain 5dp colour
bars - there is not enough height per bar for legible text, and colour alone
beats unreadable text.

Year view day taps now work. The previous version deliberately made days
non-tappable to keep the year level about picking a month; the request was to
jump to a day directly, so tapping a day in the year view opens that day.

Spacing between sections, grounded in a measurement rather than a convention.
Brumby &amp; Zhuang 2015 (CHI, DOI 10.1145/2702123.2702177 - open access, read in
full) measured 30px between items in a column and 250px between column
groups, about 1:8, and concluded that what actually governs search time is
whether the visual groups match the semantic ones - grouping unrelated items
visually slowed search down (M=3.34s semantically arranged vs M=4.23s random,
p&lt;.001). This app had 6dp between rows and 16dp between sections, only 1:2.7.
Sections are now 48dp for 1:8. The paper measured a ratio, not absolute
values, so only the ratio is borrowed and the absolute size follows the row
height here - which is more to stand on than 8dp-grid conventions: a survey
found that Gestalt proximity gives only a monotonic relation and that the
8dp grid, 44pt and 48dp+8dp figures are industry conventions with no
measurement study cited behind them.

Pie menu quadrant labels are now always visible, placed outside the circle at
1.15x the radius. They were previously only shown while held, to avoid the
occlusion that Callahan et al. 1988 (DOI 10.1145/57167.57182) recorded users
complaining about - but an always-present control with no labels gives no
reason to reach for it. Keeping them outside means the disc, its bands and
the centre W are still never covered.

Checked and NOT reproduced: black text in dark mode on Home, Timeline and
Note. The most recent captures of all three were read carefully and every
string is white or light grey. 2.31's spacing change is the only edit those
frames received in this pass. If specific text still reads as black, it needs
a screenshot that names it.
</pre>
</div>
</content>
</entry>
<entry>
<title>Phone layout: tab icons, short labels, and a working way to shoot at phone width</title>
<updated>2026-09-26T07:41:01+00:00</updated>
<author>
<name>charlie</name>
<email>root@dangofactory.local</email>
</author>
<published>2026-09-26T07:41:01+00:00</published>
<link rel='alternate' type='text/html' href='https://openpgpkey.dangofactory.net/cgit/cgit.cgi/kukuri.git/commit/?id=52a808ac2a248d4e0667674895f263a6e25dce5c'/>
<id>52a808ac2a248d4e0667674895f263a6e25dce5c</id>
<content type='text'>
The narrow layout was already implemented to 2.22's skeleton, but measuring it
at a real phone width found two problems, and getting to that measurement took
three failed approaches worth recording.

Why a Galaxy S25 user agent changed nothing: Firefox will not make a window
narrower than 500 CSS px. Requesting 380/400/412/450/500 returned 500 every
time, while 600 and 700 were honoured exactly. An S25 is 384-412 px, so the
real width is unreachable in a top-level window - the limit is the window, not
the UA. Shrinking the Compose canvas element to 384 also failed: the canvas
attribute became 384 but Compose kept laying out against 500, so the capture
showed 500-wide content cut off at 384 and the tab bar lost Live and settings
off the right edge. What works is an iframe of the exact size - it has its own
viewport, so the app inside sees innerWidth 384 and Compose lays out against
real dimensions. phone-host.html is now part of the web resources so it ships
with the bundle, and shot-phone.py drives it. Recorded as 2.29 so this is not
re-derived later.

With a true 384px viewport, the tab bar did not fit. Laying labels out with
SpaceEvenly got only Home, Timeline, Calendar and Note on screen; Live and
settings were pushed off and never drawn. Six text labels do not fit 384px.

So the tabs are now an icon over a short label, each weighted equally
(2.28):
  - icons drawn on a Canvas, no image assets: house, list, calendar, page,
    camera, gear
  - short labels, chosen rather than truncated - cutting a word mid-way makes
    it unreadable, so separate short names are used: Home / Line / Cal / Note /
    Live / Settings
  - labels stay English. One tab reading 設定 among five English ones mixes
    languages inside a single row; in-page headings stay Japanese, but the tab
    row is a list of proper names.
  - icons are never shown alone, for the same reason 2.22 rejected an
    icon-only nav: NN/g's Icon Usability (2014) found icons on their own do
    not convey meaning reliably.

Wide layout gets the same six glyphs on the left-column nav, as asked. The
same frame now looks the same at both widths, so nothing has to be relearned
when the window changes; wide has room for the full words, so it keeps
"Timeline" and "Calendar".

Calendar bar, per the same request: the centre pill on the month view is now
今月 rather than 今日 (the day view keeps 今日), and the pill is drawn with a
fill so it reads as pressable rather than as a heading. Both bars were
re-measured at 384px and fit.

One capture trap worth knowing: the browser profile is reused so the wasm
caches and startup is fast, which means a stale build can be served and the
screenshot silently shows the previous version - it did, showing "Time" and
"設定" after those strings had already changed. The URL now carries a
cache-buster that the harness forwards to the inner iframe.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The narrow layout was already implemented to 2.22's skeleton, but measuring it
at a real phone width found two problems, and getting to that measurement took
three failed approaches worth recording.

Why a Galaxy S25 user agent changed nothing: Firefox will not make a window
narrower than 500 CSS px. Requesting 380/400/412/450/500 returned 500 every
time, while 600 and 700 were honoured exactly. An S25 is 384-412 px, so the
real width is unreachable in a top-level window - the limit is the window, not
the UA. Shrinking the Compose canvas element to 384 also failed: the canvas
attribute became 384 but Compose kept laying out against 500, so the capture
showed 500-wide content cut off at 384 and the tab bar lost Live and settings
off the right edge. What works is an iframe of the exact size - it has its own
viewport, so the app inside sees innerWidth 384 and Compose lays out against
real dimensions. phone-host.html is now part of the web resources so it ships
with the bundle, and shot-phone.py drives it. Recorded as 2.29 so this is not
re-derived later.

With a true 384px viewport, the tab bar did not fit. Laying labels out with
SpaceEvenly got only Home, Timeline, Calendar and Note on screen; Live and
settings were pushed off and never drawn. Six text labels do not fit 384px.

So the tabs are now an icon over a short label, each weighted equally
(2.28):
  - icons drawn on a Canvas, no image assets: house, list, calendar, page,
    camera, gear
  - short labels, chosen rather than truncated - cutting a word mid-way makes
    it unreadable, so separate short names are used: Home / Line / Cal / Note /
    Live / Settings
  - labels stay English. One tab reading 設定 among five English ones mixes
    languages inside a single row; in-page headings stay Japanese, but the tab
    row is a list of proper names.
  - icons are never shown alone, for the same reason 2.22 rejected an
    icon-only nav: NN/g's Icon Usability (2014) found icons on their own do
    not convey meaning reliably.

Wide layout gets the same six glyphs on the left-column nav, as asked. The
same frame now looks the same at both widths, so nothing has to be relearned
when the window changes; wide has room for the full words, so it keeps
"Timeline" and "Calendar".

Calendar bar, per the same request: the centre pill on the month view is now
今月 rather than 今日 (the day view keeps 今日), and the pill is drawn with a
fill so it reads as pressable rather than as a heading. Both bars were
re-measured at 384px and fit.

One capture trap worth knowing: the browser profile is reused so the wasm
caches and startup is fast, which means a stale build can be served and the
screenshot silently shows the previous version - it did, showing "Time" and
"設定" after those strings had already changed. The URL now carries a
cache-buster that the harness forwards to the inner iframe.
</pre>
</div>
</content>
</entry>
<entry>
<title>Wasm shell: fix the Japanese font lookup so text renders</title>
<updated>2026-09-26T00:49:50+00:00</updated>
<author>
<name>charlie</name>
<email>root@dangofactory.local</email>
</author>
<published>2026-09-26T00:49:50+00:00</published>
<link rel='alternate' type='text/html' href='https://openpgpkey.dangofactory.net/cgit/cgit.cgi/kukuri.git/commit/?id=8403dd628e955bd9eb4eea6bd83540c0c1f324b5'/>
<id>8403dd628e955bd9eb4eea6bd83540c0c1f324b5</id>
<content type='text'>
The web shell never loaded its font. webfont.js stores the bundled TTF as
base64 in window.__orgflowFontB64, but the fetch/XHR shim in index.html only
looked for globalThis.__orgflowFontBlobs, which nothing ever created. Every
font request therefore fell through to the real fetch, the nested
composeResources path does not exist on the deploy server (its FTP forbids
MKD, which is why the font is shipped flat in the first place), and the app
rendered with no font at all.

Visible effect: every date number and every Japanese label came out as tofu
boxes. Confirmed by capturing the running app - document.fonts reported
"loaded n=0", and the calendar grid showed squares where the digits belong.

Fix: decode __orgflowFontB64 into the Blob map the shim expects. The decode
runs once at parse time and again lazily on the first matching request, because
webfont.js is loaded after this script and may not have run yet when the first
font read happens.

Two other things were established while chasing this, and are worth recording
because they look like application bugs but are not:

- The month grid appeared to stop at the 19th. It was not a layout bug: the
  browser chrome left only 650px of viewport, and a 7-column month grid needs
  roughly 190px per row, so six rows cannot fit. The screenshot tool now runs
  the browser in kiosk mode and sizes it to the full screen (1280x1024), and
  the grid shows the whole month plus the out-dates.
- The calendar's "today" cell looked like a two-cell red block in one capture.
  It is a single cell; the earlier reading was off because the viewport was
  small and the cells were being squeezed.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The web shell never loaded its font. webfont.js stores the bundled TTF as
base64 in window.__orgflowFontB64, but the fetch/XHR shim in index.html only
looked for globalThis.__orgflowFontBlobs, which nothing ever created. Every
font request therefore fell through to the real fetch, the nested
composeResources path does not exist on the deploy server (its FTP forbids
MKD, which is why the font is shipped flat in the first place), and the app
rendered with no font at all.

Visible effect: every date number and every Japanese label came out as tofu
boxes. Confirmed by capturing the running app - document.fonts reported
"loaded n=0", and the calendar grid showed squares where the digits belong.

Fix: decode __orgflowFontB64 into the Blob map the shim expects. The decode
runs once at parse time and again lazily on the first matching request, because
webfont.js is loaded after this script and may not have run yet when the first
font read happens.

Two other things were established while chasing this, and are worth recording
because they look like application bugs but are not:

- The month grid appeared to stop at the 19th. It was not a layout bug: the
  browser chrome left only 650px of viewport, and a 7-column month grid needs
  roughly 190px per row, so six rows cannot fit. The screenshot tool now runs
  the browser in kiosk mode and sizes it to the full screen (1280x1024), and
  the grid shows the whole month plus the out-dates.
- The calendar's "today" cell looked like a two-cell red block in one capture.
  It is a single cell; the earlier reading was off because the viewport was
  small and the cells were being squeezed.
</pre>
</div>
</content>
</entry>
<entry>
<title>UI: stop calling kotlinx-datetime's now()/todayIn() - it cannot link on Wasm</title>
<updated>2026-09-24T08:47:52+00:00</updated>
<author>
<name>Charlie Root</name>
<email>root@dangofactory.local</email>
</author>
<published>2026-09-24T08:47:52+00:00</published>
<link rel='alternate' type='text/html' href='https://openpgpkey.dangofactory.net/cgit/cgit.cgi/kukuri.git/commit/?id=481f8dde4dde37dce726ba3dc2815b13202620f4'/>
<id>481f8dde4dde37dce726ba3dc2815b13202620f4</id>
<content type='text'>
The UI still died at startup, now with a different symbol:

  IrLinkageError: Function 'now' can not be called: No function found for symbol
  'com.kizitonwose.calendar.core/now|now@kotlinx.datetime.LocalDate.Companion(
   kotlin.time.Clock;kotlinx.datetime.TimeZone){}[0]'

So replacing the calendar library's YearMonth.now() with LocalDate.now() was not a
fix - it only moved the failure one level down. Every now/todayIn entry point in
this stack is declared as taking a kotlin.time.Clock, and that symbol does not
resolve in this combination (Kotlin 2.1.21 + kotlinx-datetime 0.7.1 + Compose MP
1.8.2, Wasm target). kotlin.time.Clock only becomes usable in later Kotlin
versions, and kotlinx-datetime 0.7 is built against that newer shape.

Get "today" from the platform instead: a new expect/actual todayLocalDate() with
one actual per target - JS Date on wasmJs, Clock.System.todayIn() on jvm and
android (where the Clock symbol does resolve). The calendar month is then built
as YearMonth(year, monthNumber) from that date, which also avoids the
kizitonwose Month vs kotlinx Month type clash that broke the Android compile.

Wasm js(...) has its own constraint worth recording: the call must be the single
expression forming a top-level function body, and the result must be a
primitive/string/function - so the Date reads are folded into one arrow IIFE
returning YYYYMMDD as an Int, which Kotlin then splits.

Verified: compileKotlinWasmJs now passes.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The UI still died at startup, now with a different symbol:

  IrLinkageError: Function 'now' can not be called: No function found for symbol
  'com.kizitonwose.calendar.core/now|now@kotlinx.datetime.LocalDate.Companion(
   kotlin.time.Clock;kotlinx.datetime.TimeZone){}[0]'

So replacing the calendar library's YearMonth.now() with LocalDate.now() was not a
fix - it only moved the failure one level down. Every now/todayIn entry point in
this stack is declared as taking a kotlin.time.Clock, and that symbol does not
resolve in this combination (Kotlin 2.1.21 + kotlinx-datetime 0.7.1 + Compose MP
1.8.2, Wasm target). kotlin.time.Clock only becomes usable in later Kotlin
versions, and kotlinx-datetime 0.7 is built against that newer shape.

Get "today" from the platform instead: a new expect/actual todayLocalDate() with
one actual per target - JS Date on wasmJs, Clock.System.todayIn() on jvm and
android (where the Clock symbol does resolve). The calendar month is then built
as YearMonth(year, monthNumber) from that date, which also avoids the
kizitonwose Month vs kotlinx Month type clash that broke the Android compile.

Wasm js(...) has its own constraint worth recording: the call must be the single
expression forming a top-level function body, and the result must be a
primitive/string/function - so the Date reads are folded into one arrow IIFE
returning YYYYMMDD as an Int, which Kotlin then splits.

Verified: compileKotlinWasmJs now passes.
</pre>
</div>
</content>
</entry>
<entry>
<title>UI: wine/ink palette with a dark default, icon pie rendered per 2.5.1, and button-shaped search and controls</title>
<updated>2026-09-23T22:50:26+00:00</updated>
<author>
<name>Charlie Root</name>
<email>root@dangofactory.local</email>
</author>
<published>2026-09-23T22:50:26+00:00</published>
<link rel='alternate' type='text/html' href='https://openpgpkey.dangofactory.net/cgit/cgit.cgi/kukuri.git/commit/?id=84b54c9f7b3df2153c434d8491b92056fc350d76'/>
<id>84b54c9f7b3df2153c434d8491b92056fc350d76</id>
<content type='text'>
Colour (per user direction: "black background with white, and wine instead of
black"): the theme now has a dark default - ink (near-black) ground, warm off-white
text - with wine as the accent, and a light variant where the same wine becomes
the primary against a pale grey ground with white surfaces. Purple is gone
entirely. The [D/L] control in the bottom-left cluster toggles between them, and
Main.kt holds the appearance state. The wine itself was desaturated and deepened
(0xFF6E1B2E -&gt; 0xFF8C2438 for the dark ground) so it reads as wine rather than a
flat red on either ground.

Pie: the previous attempt got this wrong. Per 2.5.1's drawing (2026-09-22, fixed)
the pie is ONE circle divided by a cross into four quadrants, with the centre
labelled W (whole); the quadrants are the top-level scopes plus the personal
scope, which is pinned as its own quadrant because it sits outside the
organisation tree. The implementation had instead lined up three separate circles
beside the centre, which is not the specified shape at all. 2.19 also fixes the
interaction: there is no "open" gesture (touch and drag), the centre is always
whole regardless of where you press, the touched quadrant highlights while the
others dim, release commits, dragging outward expands to the concrete children
with a spoke reaching outside the circle, and returning to the centre cancels to
whole. PieMenu.kt now draws that single cross-divided circle as an icon (Canvas
arcs, dimmed/selected states, W in the centre) and tracks drag position to
quadrant, committing on release. The outward expansion to concrete children and
the 150-200ms expand / 100ms contract animation from 2.19 are the next step and
are marked as such in the file.

Chrome: search and the bottom-left controls are buttons now instead of bracketed
text - a rounded search button with a glyph, and circular +, D/L, gear buttons.
The narrow form's FAB is the same circular button.

Also fixed here: CalendarFrame no longer calls YearMonth.now(). The Wasm build
compiled it but the runtime died on load with
  IrLinkageError: Function 'now' can not be called: No function found for symbol
  'com.kizitonwose.calendar.core/YearMonth.Companion.now'
The symbol requires kotlin.time.Clock, which this Kotlin version does not resolve
for that library's binary, so the calendar library''s now() is unusable here;
building the YearMonth from LocalDate.now() gives the same value without it.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Colour (per user direction: "black background with white, and wine instead of
black"): the theme now has a dark default - ink (near-black) ground, warm off-white
text - with wine as the accent, and a light variant where the same wine becomes
the primary against a pale grey ground with white surfaces. Purple is gone
entirely. The [D/L] control in the bottom-left cluster toggles between them, and
Main.kt holds the appearance state. The wine itself was desaturated and deepened
(0xFF6E1B2E -&gt; 0xFF8C2438 for the dark ground) so it reads as wine rather than a
flat red on either ground.

Pie: the previous attempt got this wrong. Per 2.5.1's drawing (2026-09-22, fixed)
the pie is ONE circle divided by a cross into four quadrants, with the centre
labelled W (whole); the quadrants are the top-level scopes plus the personal
scope, which is pinned as its own quadrant because it sits outside the
organisation tree. The implementation had instead lined up three separate circles
beside the centre, which is not the specified shape at all. 2.19 also fixes the
interaction: there is no "open" gesture (touch and drag), the centre is always
whole regardless of where you press, the touched quadrant highlights while the
others dim, release commits, dragging outward expands to the concrete children
with a spoke reaching outside the circle, and returning to the centre cancels to
whole. PieMenu.kt now draws that single cross-divided circle as an icon (Canvas
arcs, dimmed/selected states, W in the centre) and tracks drag position to
quadrant, committing on release. The outward expansion to concrete children and
the 150-200ms expand / 100ms contract animation from 2.19 are the next step and
are marked as such in the file.

Chrome: search and the bottom-left controls are buttons now instead of bracketed
text - a rounded search button with a glyph, and circular +, D/L, gear buttons.
The narrow form's FAB is the same circular button.

Also fixed here: CalendarFrame no longer calls YearMonth.now(). The Wasm build
compiled it but the runtime died on load with
  IrLinkageError: Function 'now' can not be called: No function found for symbol
  'com.kizitonwose.calendar.core/YearMonth.Companion.now'
The symbol requires kotlin.time.Clock, which this Kotlin version does not resolve
for that library's binary, so the calendar library''s now() is unusable here;
building the YearMonth from LocalDate.now() gives the same value without it.
</pre>
</div>
</content>
</entry>
<entry>
<title>UI: implement the five-frame FSMP shell (2.22) with a month calendar, and add the Calendar CMP library</title>
<updated>2026-09-23T21:33:39+00:00</updated>
<author>
<name>Charlie Root</name>
<email>root@dangofactory.local</email>
</author>
<published>2026-09-23T21:33:39+00:00</published>
<link rel='alternate' type='text/html' href='https://openpgpkey.dangofactory.net/cgit/cgit.cgi/kukuri.git/commit/?id=1ccf22fc6eaee764fc179c8d491073441af3f5b3'/>
<id>1ccf22fc6eaee764fc179c8d491073441af3f5b3</id>
<content type='text'>
The UI module previously held a single welcome card. It now implements the fixed
skeleton from the FSMP front-end design (section 2.22, decided 2026-09-24) as
Compose Multiplatform code shared by every target:

  Home      now: TODO chips + today (timetable / same-day notices / deadlines)
  Timeline  flow: posts with answer/confirm badges and contact bubbles
  Calendar  when: month grid -&gt; tap a day -&gt; day timeline (hour-axis rows mixing
            timetable and auto-source events, per 2.21)
  Note      what remains: notes / tasks / documents / artefacts with WorkType
            filters and the unsubmitted view
  Live      now-connect: live / scheduled / recordings, teacher start button

Chrome follows the decided responsive rules rather than invented layout: the wide
form gets the left rail (title + date/period, search, the five frames in the
fixed order Home/Timeline/Calendar/Note/Live, spacer, [+][gear][D/L] at the
bottom), the narrow form gets a search field on top and a bottom tab bar with the
gear at its right end. The pie sits bottom-right on both forms (centre W, four
quadrants) and the [+] sits at its left as a pair, swapping under the
left-handed flip setting; the contact input bar appears only on wide Timeline and
carries no destination chip, because the pie itself shows scope and destination.
Frame bodies intentionally hold sample values - the design leaves in-frame
placement and visual tuning to this prototype, so only the skeleton is asserted.

Calendar dependency: kizitonwose/Calendar (MIT), artifact
com.kizitonwose.calendar:compose-multiplatform, which supports Android/iOS/js/
WasmJs/Desktop like the rest of the build. Pinned to 2.7.0 because that is the
release whose required Kotlin (2.1.21) matches this project; 2.8.0 needs Kotlin
2.2 and 2.10.x needs Kotlin 2.3 plus Compose 1.10, so either would force a
toolchain upgrade well beyond this change.

One trap worth recording: the library carries its OWN YearMonth
(com.kizitonwose.calendar.core.YearMonth, with its own now()/plusMonths()/
minusMonths() extensions) because kotlinx-datetime 0.6.2 - the version the
library declares - has no YearMonth at all. That type only landed in
kotlinx-datetime 0.7.0, which is what this project uses. Importing
kotlinx.datetime.YearMonth therefore compiles to a type mismatch inside
rememberCalendarState; the code must use the library's type. Do not "fix" this by
forcing kotlinx-datetime upward - the library's binary is bound to the 0.6.2
shape.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The UI module previously held a single welcome card. It now implements the fixed
skeleton from the FSMP front-end design (section 2.22, decided 2026-09-24) as
Compose Multiplatform code shared by every target:

  Home      now: TODO chips + today (timetable / same-day notices / deadlines)
  Timeline  flow: posts with answer/confirm badges and contact bubbles
  Calendar  when: month grid -&gt; tap a day -&gt; day timeline (hour-axis rows mixing
            timetable and auto-source events, per 2.21)
  Note      what remains: notes / tasks / documents / artefacts with WorkType
            filters and the unsubmitted view
  Live      now-connect: live / scheduled / recordings, teacher start button

Chrome follows the decided responsive rules rather than invented layout: the wide
form gets the left rail (title + date/period, search, the five frames in the
fixed order Home/Timeline/Calendar/Note/Live, spacer, [+][gear][D/L] at the
bottom), the narrow form gets a search field on top and a bottom tab bar with the
gear at its right end. The pie sits bottom-right on both forms (centre W, four
quadrants) and the [+] sits at its left as a pair, swapping under the
left-handed flip setting; the contact input bar appears only on wide Timeline and
carries no destination chip, because the pie itself shows scope and destination.
Frame bodies intentionally hold sample values - the design leaves in-frame
placement and visual tuning to this prototype, so only the skeleton is asserted.

Calendar dependency: kizitonwose/Calendar (MIT), artifact
com.kizitonwose.calendar:compose-multiplatform, which supports Android/iOS/js/
WasmJs/Desktop like the rest of the build. Pinned to 2.7.0 because that is the
release whose required Kotlin (2.1.21) matches this project; 2.8.0 needs Kotlin
2.2 and 2.10.x needs Kotlin 2.3 plus Compose 1.10, so either would force a
toolchain upgrade well beyond this change.

One trap worth recording: the library carries its OWN YearMonth
(com.kizitonwose.calendar.core.YearMonth, with its own now()/plusMonths()/
minusMonths() extensions) because kotlinx-datetime 0.6.2 - the version the
library declares - has no YearMonth at all. That type only landed in
kotlinx-datetime 0.7.0, which is what this project uses. Importing
kotlinx.datetime.YearMonth therefore compiles to a type mismatch inside
rememberCalendarState; the code must use the library's type. Do not "fix" this by
forcing kotlinx-datetime upward - the library's binary is bound to the 0.6.2
shape.
</pre>
</div>
</content>
</entry>
<entry>
<title>kukuri reset: welcome-only app shell + bridge/vendor modules + CI infra</title>
<updated>2026-09-03T10:10:18+00:00</updated>
<author>
<name>ketsuban</name>
<email>ketsuban@kukuri.dev</email>
</author>
<published>2026-09-03T10:10:18+00:00</published>
<link rel='alternate' type='text/html' href='https://openpgpkey.dangofactory.net/cgit/cgit.cgi/kukuri.git/commit/?id=20fbdb7c15fd74efc62833d295373b0ce601e9e5'/>
<id>20fbdb7c15fd74efc62833d295373b0ce601e9e5</id>
<content type='text'>
Full pre-reset history preserved on branch backup/full-pre-reset (2ccab79).
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Full pre-reset history preserved on branch backup/full-pre-reset (2ccab79).
</pre>
</div>
</content>
</entry>
</feed>
