Repository navigation
Minor Changes
-
f86a656: Dark mode and glass are easier to get right, for people and coding agents.
registry.jsonships appearance recipes (appearance): filling the page
in dark mode, a light/dark toggle, following the OS, remembering the choice
without a flash, glass and its backdrop, and making your own markup - and
Tailwind'sdark:variant - follow the provider.@fujiui/mcp0.2.0 serves
them through its newget_appearancetool.- Fixed:
registry.jsonhad no glass token values. The token scope parser
still looked for thedata-fuji-glassattribute removed in the theme/material
split, so every[data-fuji-material="glass"]value was dropped. Glass and
light-glass values are now recorded as theglassandglass+lightscopes. <html>takes the theme background once it carriesdata-fuji-theme-
set by the root provider'spersistor by the bootstrap script - so
overscroll and a page shorter than the viewport no longer show the browser's
white under a dark app. It is a zero-specificity rule, so any page background
of your own still wins.
-
f86a656:
Keyboardmirrors the physical keyboard by default, and a mirrored key now
presses its cap rather than only lighting it.captureKeyswas off by default and, when switched on, lit a cap for as long
as its real key was held. Two keyboards on one screen behaving as one is what
people expect of a soft keyboard, so it is now the default, and a mirrored
press does what a press on that cap does: the cap travels, and it clicks if the
board has a voice. Auto-repeat is excluded - a held key fires keydown over and
over, and replaying the strike and the click on each of them turns one held key
into a stutter no physical board makes.The listener is also scoped, which it was not before. A board captures only
while it is engaged: a floating board while it is open, an in-flow board
while focus is inside it. Defaulting a globalwindowlistener to on would
have meant every board on a page reacting at once - the documentation page
carries eight, so one keystroke aimed at the search field would have lit all
eight boards and clicked eight times. Click or tab into an in-flow board to
engage it;captureKeys={false}opts out entirely.A mirrored press deliberately does not call
onKeyPress. The real key has
already been delivered to whatever had focus, so reporting it again would type
every character twice. -
f86a656:
Keyboard: held caps repeat, and ⌘/Ctrl/Alt work as shortcut modifiers.- Hold to repeat. Holding a cap - Backspace, a letter, an arrow - now repeats
it like a real key: one press, a 400ms pause, then twenty a second until it is
released or the pointer slides off. Previously a cap reported once, on
release, however long it was held. Modifiers and toggles never repeat, and the
click that ends a hold reports nothing extra. - Shortcut modifiers. ⌘, Ctrl and Alt caps now latch like Shift: click ⌘,
then A, and the board reports ⌘A. The latched cap lights and announces
aria-pressed; the next ordinary cap spends it. A physically held modifier
counts too while the board is engaged. onKeyPress(key, modifiers). The callback gains a second argument,
{ shift, meta, ctrl, alt }(exported asKeyboardModifiers). Under ⌘ or
Ctrl a cap reports novalue, since a shortcut types nothing - a handler that
only appendskey.valueno longer types an "a" for ⌘A.
Non-breaking at runtime: existing one-argument handlers keep working. A test
assertingtoHaveBeenCalledWith(objectContaining(...))ononKeyPressneeds a
second matcher, e.g.expect.anything(). - Hold to repeat. Holding a cap - Backspace, a letter, an arrow - now repeats
-
f86a656: Give
Keyboardits own sound toggle.soundwas a one-way prop: a board either clicked or it did not, and the only
way to let someone silence one was for the app to build a control and wire it
up. Every app that wanted the obvious affordance built the same switch.soundTogglenow renders that control on the board itself, in a strip above
the caps: a speaker glyph plus a lamp lit in the board's owntone- the same
signal a latched Caps Lock gives, for the same reason (state on this board is a
light, never a redrawn legend). Two channels carry the state, and the icon
changes shape rather than only colour. The strip is row 1 of the cap grid, so
it inherits the deck's width and padding, and everything in it is sized from
--fuji-key-unitwith pixel floors, so it stays in proportion from a 30px-cap
diagram to a 92px-cap docked board. The control clears the 24px touch-target
floor at every board size.It defaults to
interactive, so a board you can press has the control and the
static diagram of a shortcut - which has nothing to sound and no business
growing chrome - does not. PasssoundToggle={false}where the app already
offers the preference itself, so it is not presented twice. Because the strip
is drawn above the caps, it is also the board's first tab stop; focus order
follows visual order.soundkeeps working unchanged, and now has the usual uncontrolled half -
defaultSoundplusonSoundChange- so an app that already owns a "keyboard
sounds" preference can keep owning it while the board draws the control.In development the board also calls out the one wiring that produces a dead
control: a visible toggle against a controlledsoundwith noonSoundChange
reports every press to nobody, so the button moves nothing and the board keeps
clicking.useControllableStatecannot catch that on its own - controlled with
no handler is legitimate for a read-only value, and only becomes a defect once
the component draws a control for it. The symptom is a toggle that works on
some boards and not others, which reads as a flaky component rather than as a
miswired prop.
Patch Changes
-
f86a656: Fixed:
BottomNavigationreported the wrong index for items after a centre
action. With anaction, the bar splits its items around the raised
button, and the second half was numbered from 0 again - so in a four-item bar
onItemSelectreported "Friends" as index 1 and "Playing" as index 0, and an
app switching screens by index opened the wrong one. Each item now reports its
own position initems. -
f86a656:
Card effect="tilt"leans more gently: at most 3deg at the corners (was 7deg), under a 1200px perspective (was 500px), with a 4px push-back (was 10px). A large card no longer swings visibly out of plane. -
f86a656: Align
ChatBubble's avatar with the bubble instead of with the metadata under
it.The row was
flex items-end, so the avatar bottom-aligned with the whole
message column - sender, bubble, and the timestamp/status line together. Any
bubble carrying metadata therefore pushed its avatar down past the bubble and
level with the timestamp, which reads as a layout bug at every avatar size and
gets worse as the avatar gets smaller: a 32pxsize="sm"avatar beside a
timestamp row ended up almost entirely below the bubble it belonged to.The row is now a grid with the avatar in its own column on the bubble's row, so
it is pinned to the bubble's bottom edge whatever else the column holds. The
vertical rhythm moved from agap-yto margins on the sender and metadata rows,
because a grid gap is paid between tracks even when one of them renders nothing.Grouped bubbles also keep the avatar in the DOM and hide it, rather than
swapping in a fixed 32px placeholder. The slot takes an arbitrary node, so the
avatar's own box is the only footprint certain to match the rest of the run -
the old placeholder silently misaligned every run built with the default 40px
Avatar.The tail no longer seams against the bubble either. The two are separate boxes
carrying the same opaque fill, and they used to meet exactly on the bubble's
edge - so wherever that edge landed on a fractional pixel, both were
antialiased against it and composited in turn, bleeding roughly a quarter of
the page colour through the join. Against light's near-identical page and
bubble that is invisible; in dark it drew a visible hairline between the tail
and the bubble, which is why the same build could look clean in one layout and
seamed in another. The tail now runs 1px under the bubble, so the two never
share an antialiased edge. Its visible silhouette is unchanged.