Skip to content

Scale the native window with the Hyprland monitor factor - #65

Closed
NT7S wants to merge 512 commits into
BackplaneWorks:mainfrom
NT7S:backplane/monitor-ui-scale
Closed

NT7S wants to merge 512 commits into
BackplaneWorks:mainfrom
NT7S:backplane/monitor-ui-scale

Conversation

@NT7S

@NT7S NT7S commented Oct 12, 2026

Copy link
Copy Markdown

The native window is an X11 client with a fixed pixel layout. On Hyprland with xwayland.force_zero_scaling, the compositor does not scale that window, so 14 px text stays 14 physical pixels and is hard to read on a scaled monitor.

The layout and pointer events stay in logical pixels. The X window and its framebuffer are that size times the monitor scale. BACKPLANE_UI_SCALE overrides the factor. When it is unset, the focused monitor's scale: from hyprctl monitors is used. A value near 1, or no Hyprland, keeps the old 1:1 path, including the 64 px dirty blocks. Any other scale (0.5 to 4) resamples the frame bilinearly in linear light, so a fractional factor does not stair-step glyphs. BACKPLANE_SNAP still writes the logical frame.

The web client and the phone apps are unchanged. They already follow CSS and device pixels. This is only the X11 framebuffer.

Checked on a Hyprland session at scale 1.6: the process logged UI scale 1.6 (window 2240x1440 for a 1400x900 layout), and a glyph edge blended through intermediate grays. That build was this win.c on the v0.11.1 tree; win.c at that tag is the same as current main before this change. Bend sources are untouched, so bend PROOF.bend was not re-run.

i2cjak and others added 30 commits September 29, 2026 18:15
…itor or workflow still running shows Monitoring, not Complete

Claude's task_started lines for local_bash, local_workflow, remote_agent and monitor tasks now join the thread's bg list (not a subagent's own, not a foreground shell), and a task_updated that moves a shell to the background past its timeout adds it too.
…-shells

Threads monitor background shells: a turn that ends with a shell, monitor or workflow still running shows Monitoring, not Complete
…and its steps; a subagent's own lines go to its log, not the timeline; sidebar rows say N agents
…isibility

Subagents in view: a live panel in every client, their steps off the timeline, N agents in the sidebar
…tching; desktop notifications on the desk used last

The window and the web page say which thread they show and whether they have
the focus (desk.look, core/desk.bend). A thread a focused desk shows, used in
the last 3 minutes, raises no APNs push and no phone alert (info "desk"); any
other alert also goes to the desk used last as {"t":"notify"}: the browser's
Notification API (macOS, Windows, Linux) or notify-send in the Linux window;
a click opens the thread. Laws desk_*, alert_watched_quiet; test/desk_test.bend,
test/tools/desk_e2e.ts.

Co-Authored-By: Claude <noreply@anthropic.com>
Share ordinary projects and threads through the far projection, label their machine and availability on every client, and deduplicate paired-owner phone catalogs and folded counts. Preserve far drafts and forward the owning hub actual RPC reply.

Track remote history epochs, replace only the affected projection on reset, serialize mirror persistence, and prevent late startup disk loads from replacing live traffic. Add 19 laws and projection, phone, routing and recovery coverage.
…tifications-setup

Desks: hold phone alerts for a thread you're watching; desktop notifications
A directory-only bot choice gains its mirrored thread when available, without replacing an unfinished composer; native keyboard focus follows. Add four laws for selection preservation, adoption and focus.

Offer the current history head on newly discovered ownership and establish the epoch before publishing pushes. Extend two-hub coverage to lower-sequence history replacement and delayed old pushes, and read created ordinary thread IDs from events.
…back-animate

Design history in the viewer: walk, play and compare every agent edit
…open on the hub's machine; right-click (a tap on phones) for Open, Show in folder, Copy path

Co-Authored-By: Claude <noreply@anthropic.com>
…file-links

File links in messages: open, show in folder, copy path
…lient (core/tools.bend)

Stop, then pin/settle/snooze/archive/delete, then Fork thread/Bring back,
then Terminal/Panel, each an icon and a label with a tooltip; the window
drops to icons only when narrow. Viewer is Panel; the header's separate
Diff button goes (Diff is the panel's tab, ctrl+d and the palette).
Phones' menus take the same icons. Laws tools_*.
…board

The hub sends a view with "auto" at each Claude, Codex or Grok start
(hub.bend's Auto.view, server.bend's Hub.auto); a client takes it only
while the thread's viewer has shown nothing and its panel was never
toggled by hand (Viewer.auto, panel.hand:<t>). Laws viewer_auto_*,
auto_view_none; test/tools/auto_panel_e2e.ts.
…design

Thread header: icons and clearer buttons; KiCad projects open the panel
…android-fix

Android: fix the tool icon parse
…ad-performance

# Conflicts:
#	AGENTS.md
#	src/app/bots.bend
#	src/app/layout.bend
#	src/app/main.bend
#	src/core/client.bend
#	src/mobile/screen.bend
#	src/server/server.bend
#	src/web/view.bend
… across updates

A phone rebuilt its whole screen for every token of every agent at work
(380 ms each on a phone's engine, no JIT), even for threads it was not
showing. Now:

- The phones' bundle compares strings natively (scripts/bridge-native.py,
  run by build-mobile.sh): Bend's char-at-a-time String.cmp was over half
  of every screen. The list's screen 174 -> 46 ms, a thread's 233 -> 97 ms,
  a cold launch folding an 11.7 MB log 13.9 -> 8.9 s.
- The screen's id walk turns most keys away with one hash (Q.kind,
  law q_kind_names); a sidebar row looks up its thread's asks once;
  the image scan stops once done instead of walking the whole log.
- No screen for another thread's streamed text (Hubs.shows, laws
  hubs_delta_elsewhere, hubs_delta_other_thread): 5 ms a token, was 360.
  A screen an app dropped as stale is owed to the next frame (owe in
  bridge.js and both Engines).
- The hub merges a read's run of Claude text deltas into one, and a read
  that ends mid-stream waits 60 ms before the next (Delta.merge,
  Delta.streaming; test/delta_test.bend): with 10 agents streaming a client
  gets 136 frames/s, was 428, at most ~60 ms later.
- WebSockets are TCP_NODELAY: a request's round trip p99 62 -> 22 ms.
- The kept-state key covers only the types the kept state holds, so an
  update that changes other types keeps every phone's state and it asks
  its hubs only for what is new (1.5 KB, 57 ms on the benchmark log).

Benches: test/tools/phone_screen_bench.ts, test/tools/phone_stream_bench.ts.
…-performance

Phones: fast screens, a frame per burst not per token, state kept across updates
i2cjak and others added 26 commits October 6, 2026 10:43
…in-multi-account

Phones: Google sign-in from the app, any number of accounts
…s own repository and never a hidden file, cache rounds ordered by a ticket taken as their list arrives (a restored older file is installed, a stale round installs nothing), a failed round waits before the next, copy paths never prefixed twice (child sheets), a comparison's working copy read from the copy; bots_test schema text
…backplane

P2P Backplane: create, drive, stream and view threads across all machines and clients
…und for it (or BACKPLANE_URL), never the 0.0.0.0 it listens on
Pairing off the tailnet: name a reachable address, not 0.0.0.0
Long threads: open, scroll and load fast on web, phones and window; todos and subagents fold and scroll
src/core/layers.bend (pure): a board's layers in KiCad's order (copper
front to back, pads, vias, holes, the technical layers in front/back
pairs, the user layers, edge and margin, courtyards, fab) with KiCad 7+
names; the focus (an active layer and how the others show: as they are,
dimmed to a quarter, hidden), what stays with the active layer (the
outline, and a copper layer's pads, vias and holes), the masks the web
and phones draw from, a click on a layer's name (it stands out; again,
all back), the All/Dim/Only switch, ctrl+H's cycle, and the window's
kind word carrying the focus. Laws lyr_*; test/layers_test.bend.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…/Only

Each row is the layer's colour (a click shows or hides it) and its name
(a click makes it the active layer and dims the rest; again, all back).
Over a board's rows, All, Dim and Only say how the others show, and the
menu's button names the active layer. The canvas gets the hidden and
dimmed layers as bits and the layer plot2d.js draws last, on top
(client.bend's Lfocus, actions view-layer-pick and view-layer-mode).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n KiCad

The Layers list follows KiCad's order and names; a row's colour shows or
hides it (vw-layer), its name makes it the active layer (vw-layer-pick),
and a board's list starts with All/Dim/Only (vw-layer-mode). ctrl+H cycles
them as KiCad's high contrast does. The focus rides in the viewer's kind
word into V.Look.on (Lyr.shade) and picking, and Ops.scene draws the
active layer after the rest. Reset layers clears the focus too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The phone's layer list follows KiCad's order and names and marks the
active layer; screen fields dim, lift, layerMode, layerModes and
layerHead, and off now holds the layers the focus hides too (a tap does
not pick them). Android: a row's box shows or hides the layer, its name
singles it out, chips for All/Dim/Only; the GL renderer draws dimmed
layers at a quarter and the active one last. iOS: the same in its menu
(a segmented picker, each row's show/hide and stand-out buttons) and
Metal renderer (not built here).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Look.on read the focus from the kind word, so look_flip_none (with no
flips Look.on is the kind's own look, for every kind) no longer held for
a kind carrying a focus, and the proof failed in CI. Look.on and Look.of
are back as they were; Look.at is Look.on under the focus, and the scene
(Look.alpha, so Look.shows and the ops' gate and picking) and an item's
colour go through it. A kind with no focus looks as Look.on says. bend
PROOF.bend: All terms check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Board layers: KiCad's order, single out a layer, All/Dim/Only (all clients)
…name: a merged PR is the thread's when its head commit is one the worktree made (HEAD reflog: commits, cherry-picks, rebased picks; never checkouts)

Co-Authored-By: Claude <noreply@anthropic.com>
….max), the rest counted; one line a thread, one footer line

Co-Authored-By: Claude <noreply@anthropic.com>
…processes; a restart or a kill is picked back up by the hub
…merge-by-commit

Settle merged threads whatever the PR's branch is named
iOS Live Activity: fit the lock screen with many threads
Remove systemd from Backplane: agents and work_run run as plain processes, restarts are picked back up
A moving cat in the sidebar asked for a whole frame every 33 ms while
someone was at the window. Each frame took about 45 ms of layout, plan
and raster, so the live window ran ~20 frames a second at 170% CPU and
keys and the sidebar's wheel waited behind them. Input now holds the
cats still for Bv.calm (1 s); the cat timer asks for the frame after it.
Law bots_cats_calm_on_input.
Hold the cats still while the person types or scrolls
- src/core/msp.bend: MSP translator (initialize, session/start|resume,
  setApprovalMode, turn/start; deltas, tool calls, todos, turn end; approvals
  as receipts + approval/decide), command ids made from the turn's gen
- server: Grok's per-turn runner generalised over MS.Proto (ACP or MSP);
  Muse.go/Muse.args; model/list probe and labels
- hub: MuseTurn, dispatch index 3, tool.done steps for muse
- mcp: approval/request routed to the person (Muse.permit), decide on answer
- agent: effort levels, text provider muse (muse exec), family muse-*
- clients: muse chips in window, web, phones, bots; onboarding text
- laws interrupt_muse_stops, muse_run_turn, msp_mode_*; test/msp_test.bend;
  e2e stand-ins put a fake muse on PATH; docs
…ode-backend

Muse Code as a fourth agent provider
Hyprland's xwayland.force_zero_scaling leaves an X11 window in physical
pixels, so a fixed layout draws tiny text on a scaled monitor. The app
still lays out in logical pixels. The X framebuffer is that size times
the focused monitor's scale (BACKPLANE_UI_SCALE overrides; hyprctl
supplies the factor otherwise). A fractional scale is resampled
bilinearly in linear light. Scale 1 keeps the previous path, including
the 64 px dirty blocks.
@NT7S

NT7S commented Oct 12, 2026

Copy link
Copy Markdown
Author

This is my first AI-assisted PR, so if there's a problem fell free to trash it. I needed to be able to read the text on a higher-DPI display and thought I should at least try to give back since I had Grok build me a fix.

@i2cjak

i2cjak commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Closing this obsolete X11-window patch: the C window backend and win.c are retired; the maintained desktop is Electron and follows browser/device scaling.

@i2cjak i2cjak closed this Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants