Skip to content

feat(office): put each agent's display name on a nameplate at its feet - #341

Merged
chaitanyagiri merged 3 commits into
HarnessMD:mainfrom
savvaskoualis:feat/floor-nameplates
Oct 2, 2026
Merged

chaitanyagiri merged 3 commits into
HarnessMD:mainfrom
savvaskoualis:feat/floor-nameplates

Conversation

@savvaskoualis

@savvaskoualis savvaskoualis commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

What & why

The floor shows what every agent is doing and never who it is. Each avatar carries a live action bubble ($ using Bash, awaiting) but no name, so past two or three agents identifying anyone means clicking avatars until you hit the one you meant. In the before shot below, two avatars sit side by side both reading "heading to terminal" with nothing to distinguish them. A rename makes it worse: nothing on the floor reflects the new name.

This adds a persistent nametag under each avatar carrying its roster display name. It rides the sprite's own container, so it moves, scales and fades with the avatar with no per-frame position code, and it re-reads the store on every tick so a rename shows up without a restart.

Below the feet, never above — that space is already spoken for twice: the thought bubble only ever floats upward from the head, and updateStatusGlyph draws at y = -34.

Closes #340

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Docs
  • Build / CI

Evidence

Before

before

Three agents at desks; two of them show the identical bubble "heading to terminal".

After

after

Same floor, same window size, same seeded roster. Four at desks, three of them mid-"heading to board" — now readable at a glance. The action bubbles above the heads are untouched.

How I tested it

  • OS: macOS 26.6.2, Apple silicon
  • Steps: built with npm run build and ran the app against a throwaway --user-data-dir seeded with five demo agents, so no real hive or agent data is in frame. Captured both states with Playwright's _electron win.screenshot() (never macOS screencapture, which can catch other windows). Confirmed the tag follows the avatar while walking and sitting, and that renaming an agent updates it live without a restart.

Discord (optional)

Discord:

Checklist

  • Before and after evidence is attached above, under both headings.
  • npm run typecheck passes.
  • npm run test:focused passes (552/552).
  • npm run build succeeds.
  • This PR is one change. Unrelated fixes belong in their own PR.
  • I read the diff myself before opening this, and there is no debug output, commented-out code, or unrelated formatting churn in it.
  • Any new UI derives from DESIGN.md / tokens.ts — no ad-hoc colors, spacing, or fonts.
  • No new art added.

savvaskoualis and others added 2 commits August 26, 2026 22:36
The floor showed only a live action bubble ("$ using Bash", "awaiting")
per agent, with no way to tell who is who without clicking — three
avatars in a row can all read "heading to board" and be
indistinguishable. Add a persistent nametag riding the sprite's own
container, so it moves, scales and fades with the avatar for free,
synced on every store tick so a live rename shows up without a restart.

Below the feet, never above. The space over the head is already taken
twice: the thought bubble floats upward from there, and
updateStatusGlyph draws at y=-34.

Colours come from tokens.ts (cream/ink) rather than raw hex, and the
three tunables are named constants — the idiom ThoughtBubble next door
already uses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Captured with Playwright's _electron win.screenshot() against a fully
isolated scratch harness — invented role-label agents, throwaway
user-data-dir, never the real hive and never macOS screencapture, so no
other window's content can be in frame.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

✅ Evidence received. Before and after are both attached. Thanks — this is what makes a PR reviewable in one pass.

mrlfarano added a commit to mrlfarano/munder-difflin that referenced this pull request Aug 31, 2026
@chaitanyagiri

Copy link
Copy Markdown
Collaborator

Reviewed by an agent on the Munder Difflin hive floor, posted from @chaitanyagiri.
First person statements below are the agent's, including the scope limits.
Replies here are read. Questions in this comment are real questions.
Base: origin/main at 956bfb4.


Closes #340. Three decisions in here are right and each has its reason written down: the tag is a
child of the sprite container so it moves, scales and fades with the avatar for free; it sits
below the feet so it can never collide with the thought bubble, which only ever floats up; and
setName is a no op when unchanged so calling it on every store tick costs nothing. The rename
path is handled, which is half of what #340 actually complains about.

this.nameTag.eventMode = 'none' is the detail that usually gets missed. Without it the label
would swallow clicks meant for the avatar underneath.

One inconsistency: the diff adds import { colors } from '@/design/tokens' and uses it for both
the fill and the stroke, then hardcodes fontFamily: 'monospace'. The generic family resolves to
whatever the platform picks, which is a different face on macOS, Windows and Linux, while
everything around it is on tokens. If there is a mono token, use it. If there is not, a one line
comment saying the generic is deliberate would stop the next person changing it.

The thing I could not judge from a still: NAME_TAG_OFFSET_Y = -4 puts the label into the tile in
front of the sprite, so a character standing directly below another can have its head under the
one above's nameplate. At 6px on one line that may be invisible. The evidence pair shows a spread
out floor. If you have a screenshot with two avatars stacked vertically, that is the one worth
attaching.

The nameplate rode the sprite's own container, which made it free to move
but put it at the mercy of whoever stood one tile south. Avatars are 34.56px
tall in a 32px tile, so vertical neighbours always overlap, and at 6px mono
any name past ~5 characters is wider than the 19.44px sprite it labels:
"Frontend" is 28.8px, 1.48x the avatar, and "Architect" is 1.67x. Two
adjacent tags rendered as interleaved glyphs, legible as neither name. No
value of NAME_TAG_OFFSET_Y fixes that - the sprites themselves overlap.

The tag is now world-positioned in the character layer at its own zIndex
band above every sprite and below the thought bubbles, with an opaque
rounded plate sized to the measured text. Tags stay y-sorted among
themselves, so the nearer agent's name wins the overlap cleanly instead of
the two interleaving. The cost is deliberate and accepted: a name can cover
the top of the head of whoever stands directly below.

Following the avatar is now explicit per-frame position code rather than
free, alongside the thought bubble's own sync in the same update pass.
Alpha tracks the sprite so it still fades in and out with the avatar, and
the tag is removed from its parent on fade-out and on destroy.

Also states why fontFamily is the bare generic: ThoughtBubble and ToolBubble
both use 'monospace' for the same reason, and a nameplate in a different
face from the bubble directly above it reads as a bug. Deliberately not
type.mono - move all three together or none.

Evidence captured from the built app on a seeded 15-agent floor.

552/552 focused tests, both tsconfig targets clean, build succeeds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@savvaskoualis

Copy link
Copy Markdown
Contributor Author

Both checked against the built app rather than the diff. The second one was a real bug — thank
you for pushing on it.

The stacked screenshot you asked for

I built the branch, seeded a throwaway 15-agent floor and drove the real Electron app with
Playwright's _electron (never screencapture). You were right, and it is worse than "a head
under a nameplate":

Before

stacked before

That is "Frontend" seated with "Growth" standing one tile south. The two tags interleave into
something legible as neither name. On the same floor "Docs" came out bisected and buried.
Isolated avatars were fine — which is exactly why the spread-out evidence pair didn't show it.

But the offset is not the cause, so no value of NAME_TAG_OFFSET_Y fixes it. Two measurements:

  • Avatars are 18 × 32 at CHAR_SCALE = 1.08 → 34.56px tall in a 32px tile. Vertically
    adjacent sprites always overlap by 2.56px. There is no gap to put a label in.
  • At bold 6px monospace, any name past ~5 characters is wider than the 19.44px sprite it
    labels: Docs 14.4px (0.74×), Backend 25.2 (1.30×), Frontend 28.8 (1.48×), Architect
    32.4 (1.67×).

So the tag was always going to leave its own footprint and land on a neighbour. Riding the sprite
container — the thing I wrote up as a win — is what made it lose: parented there, it renders
under whoever sorts in front.

5dc1924e moves the tag into the character layer at its own zIndex band, above every sprite and
below the thought bubbles, with an opaque rounded plate sized to the measured text. Tags stay
y-sorted among themselves, so the nearer name wins the overlap cleanly.

After

stacked after

Two avatars on vertically adjacent tiles, both names readable. The cost is deliberate: the front
tag covers the top of the head behind it. Given the point of #340 is knowing who is who, a
covered scalp beats an unreadable name — say if you'd rather it fade out when crowded instead.

Following the avatar is now explicit per-frame code rather than free, sitting beside the thought
bubble's own sync in the same pass. Alpha still tracks the sprite, and the tag is removed on
fade-out and on destroy.

One overlap I did not change: thought bubbles still sort above nameplates, so a bubble can
still cover a name. That is the pre-existing layer order, not something this PR introduced, and
flipping it means tiny plates punching holes in the speech clouds. Happy to flip it if you think
identity should outrank the bubble.

The mono token — you're half right, and I'd rather not take the fix

type.mono does exist (design/tokens.ts:62). But the two other in-world Pixi labels hardcode
the same bare generic:

  • ThoughtBubble.ts:87 — fontFamily: 'monospace'
  • ToolBubble.ts:81 — fontFamily: 'monospace'

So "everything around it is on tokens" holds for the DOM layer, which is all on var(--cth-font-*),
but not for the scene layer, where the generic is the standing convention. Switching only the
nameplate would make it the one label on the floor in a different face — and it sits directly
under a ThoughtBubble, so the mismatch would be visible in the same glance.

I took your second option and wrote the reason down in 5dc1924e rather than leave the next
person guessing. If the generic is genuinely wrong it is wrong for all three, and that is its own
PR touching two files this one has no business in.

While we're here: the "no ad-hoc colors, spacing, or fonts" checklist box was ticked wrongly
either way. That's on me.

552/552 focused tests, both tsconfig targets clean, build succeeds.

@chaitanyagiri
chaitanyagiri merged commit 68bf1af into HarnessMD:main Oct 2, 2026
3 checks passed
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.

Avatars carry a live action bubble but no name — telling agents apart on the floor means clicking each one

2 participants