Documentation
¶
Overview ¶
Package chat is crates/hover-chat: the chat thread laid out as positioned text boxes and shapes, with selection, copy, scrolling and a painter. It follows the box model of web/office/page.html's #thread, .you, .who, .ans and .md rules.
Index ¶
- Constants
- Variables
- func Avatar(out *[]Shape, x, y, w, h, r float32, c Rgba)
- func Clock(ms float64) string
- func Credits(n float64) string
- func EaseOut(x float32) float32
- func Hm(ms float64, offsetMin int64) string
- func LogoSVG(tool string) string
- func Took(ms float64) string
- type Act
- type ActKind
- type Bar
- type BarID
- type Block
- type Bot
- type BoxBar
- type Boxed
- type Fetch
- type FetchKind
- type Frag
- type HL
- type Hit
- type HitKind
- type HitRect
- type ImageKind
- type ImageState
- type Images
- type Link
- type Loader
- type Look
- type NumLine
- type Painter
- type Part
- type Pos
- type Rect4
- type Rgba
- type Scroller
- type Section
- type Shape
- type ShapeKind
- type Shaper
- type Smooth
- type Stage
- type Step
- type StepIcon
- type Tail
- type TextBox
- type Thread
- func (th *Thread) AccessibleBlocks() []Block
- func (th *Thread) AnswerText(i int) string
- func (th *Thread) BoxAt(x, y float32) ([2]int, bool)
- func (th *Thread) Document(src string, width float32)
- func (th *Thread) HBars() []BoxBar
- func (th *Thread) Hit(x, y float32) Hit
- func (th *Thread) ImageChanged(src string) bool
- func (th *Thread) OffsetOf(section int, t *TextBox) float32
- func (th *Thread) OpenDiff(turns []Turn, section, j int) (float32, bool)
- func (th *Thread) ParagraphAt(p Pos) (Pos, Pos, Tail)
- func (th *Thread) ScrollBox(id [2]int, pos float32)
- func (th *Thread) Select(anchor, focus Pos)
- func (th *Thread) SelectAll()
- func (th *Thread) SelectUnits(a0, a1 Pos, at Tail, focus Pos, u Unit)
- func (th *Thread) SelectedIn(section, text int) (int, int, bool)
- func (th *Thread) SelectedText() string
- func (th *Thread) SelectionRects(section, text int) []struct{ ... }
- func (th *Thread) Set(turns []Turn, width float32)
- func (th *Thread) SetCopied(turns []Turn, t *string)
- func (th *Thread) ToggleFlag(turns []Turn, section, j int, k uint32)
- func (th *Thread) ToggleGroup(turns []Turn, ti, first int)
- func (th *Thread) ToggleStep(turns []Turn, section, j int, now bool)
- func (th *Thread) ToggleSteps(turns []Turn, i int)
- func (th *Thread) TrailingSpace(p Pos) Pos
- func (th *Thread) UnitAt(p Pos, u Unit) (Pos, Pos, Tail)
- func (th *Thread) UseImages(im *Images)
- func (th *Thread) WordAt(p Pos) (Pos, Pos, Tail)
- type Tok
- type TokKind
- type Turn
- type Unit
Constants ¶
const ( // Thick is the bar's thickness, and each arrow button's length along the bar. Thick = 10 // LineStep: an arrow button scrolls by 40 px (Chromium's line step); the track by 87.5 % of a page. LineStep = 40 )
const ( Sans = `Inter, "Segoe UI Variable Text", "Segoe UI", system-ui, sans-serif` Mono = `"Cascadia Code", Consolas, "DejaVu Sans Mono", monospace` Pixel = `"Pixelify Sans", "Cascadia Code", Consolas, monospace` )
The page asks for Inter first, but Inter is loaded only into WPF, never into WebView2, so the office's text is really the next family the system has: Segoe UI Variable Text on Windows 11, Segoe UI on 10. The native chat resolves the same list against system fonts only (Hover's bundled Inter is not registered here) to land on the same face.
const ( ThreadGap = 7 // .you, .ans { font-size: 12.5px }, .ans { line-height: 1.5 } Body = 12.5 BodyLH = 1.5 )
Variables ¶
var ( Ink = a(0xf6f2ff, 1.0) Dim = a(0xf6f2ff, 0.62) Faint = a(0xf6f2ff, 0.38) Line = a(0xffffff, 0.09) Li = a(0xc4a2ff, 1.0) Ok = a(0x30d158, 1.0) Bad = a(0xff453a, 1.0) LinkLine = a(0xc4a2ff, 0.4) CodeBG = a(0xffffff, 0.08) PreBG = a(0x000000, 0.35) FigureBG = a(0x000000, 0.25) ImgBG = a(0xffffff, 0.04) // .md img { background: rgba(255,255,255,.04) } ThBG = a(0xffffff, 0.05) QuoteBar = a(0xc4a2ff, 0.4) YouBG = a(0x24212a, 1.0) // .me: graphite, not purple. YouEdge = a(0xffffff, 0.06) Selection = a(0x3390ff, 0.45) // Chromium's selection colour on a dark page. DrawerBG = C(22, 14, 26, 255) ScrollTrack = a(0xfcfcfc, 1.0) // The thin Fluent scrollbar, as Chromium draws it. ScrollThumb = a(0x8b8b8b, 1.0) ScrollThumbH = a(0x636363, 1.0) Visor = a(0x121018, 1.0) Eye = a(0xaaf6ff, 1.0) Bulb = a(0xffd24a, 1.0) )
The chat's look, taken from web/office/page.html. The values are those of the notch (the page's `max-height: 620px` rules apply there, because the office opens 280-600 tall). Sizes are CSS px; the painter multiplies them by the display scale.
var Bots = [6]Bot{{"Pip", C(0x9b, 0x6b, 0xff, 255)}, {"Juno", C(0x2f, 0xc9, 0xb0, 255)}, {"Moss", C(0xff, 0x9a, 0x4a, 255)}, {"Nova", C(0xff, 0x6f, 0xae, 255)}, {"Ada", C(0x5a, 0xa8, 0xff, 255)}, {"Rue", C(0xb4, 0xe0, 0x4a, 255)}}
Bots is main.js BOTS: the six bots' names and colours, by `bot` index.
var ThreadPad = [4]float32{8, 12, 4, 12}
#thread { padding: 8px 12px 4px; gap: 7px }, as top, right, bottom, left.
Functions ¶
func Avatar ¶
Avatar is `.av`: the mascot's head as a little avatar (visor, two eyes, antenna bulb).
func Credits ¶
Credits is main.js `credits`: what a turn cost, "0.09 credits", and "<0.01 credits" for less.
func Hm ¶
Hm is main.js `hm`: toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' }) as en-US writes it ("1:47 PM"), at the given offset from UTC in minutes.
Types ¶
type ActKind ¶
type ActKind uint8
ActKind is what a click on a drawn control does.
const ( // ActCopy is a Copy button: Text is what it copies (a code block, a diff, an answer). ActCopy ActKind = iota + 1 // ActStep: a step with a change or output opens or folds: I is the step, Now the "now" row. ActStep // ActFlag is one of a step's own switches: I is the step, K which. 0 shows all of a long // change, output or thought; 1 the subagents past the fourth; 2 + k subagent k's result. ActFlag // ActOpenDiff: a file under the answer: its change opens in the timeline (I is its step). ActOpenDiff // ActRetry: the newest turn's Retry: its prompt goes again. ActRetry // ActRestore: back to just after this turn's answer. ActRestore // ActTryAgain: back to just before this turn's message, which goes again. ActTryAgain // ActEdit: a queued reply taken back into the composer to be changed. ActEdit // ActEditPrompt: a sent prompt's Edit: its words go into the reply box to be changed and sent again. ActEditPrompt // ActSendNow: a queued reply sent now, ahead of the others. ActSendNow // ActGroup: a group's header: its steps (from step I) open or fold. ActGroup )
type Bar ¶
type Bar struct {
Vertical bool
X, Y float32
Len float32
// Content is the content's length, View how much of it shows, Pos how far it is scrolled.
Content, View, Pos float32
}
Bar is one scrollbar: where it is (x, y along the top or left edge, and its length) and what it scrolls.
func (Bar) Drag ¶
Drag is the scroll position for a thumb dragged so that the point grab px into it is at along.
type Block ¶
Block is one text box for assistive technology: its text, its rectangle in thread coordinates, and the part of it that is selected (bytes).
type BoxBar ¶
Bars are the sideways scrollbars, in thread coordinates, with their (section, scroller).
type Boxed ¶
type Boxed struct {
// contains filtered or unexported fields
}
Boxed is a block's box: its collapsible outer margins and its border-box height.
type HL ¶
HL is a coloured stretch of a code block: bytes B0..B1, and whether it is drawn italic.
func Highlight ¶
Highlight is a small tokenizer for the common languages, picked by the fence's language or a file's extension: keywords, calls, capitalised types, numbers, strings, comments. ponytail: a lexer per language would get every case (raw strings, nested comments, heredocs); this gets the common ones, and an unknown language stays plain.
type ImageState ¶
ImageState is an image as the layout sees it. A pending and a failed image look the same in the page (Chromium shows the broken-image icon and the alt text for both).
type Images ¶
type Images struct {
// contains filtered or unexported fields
}
func NoImages ¶
func NoImages() *Images
NoImages loads nothing (tests, and the headless screenshots unless told otherwise).
func (*Images) Forget ¶
Forget drops an image, so it is asked for again (a retry, or a file that changed).
func (*Images) State ¶
func (im *Images) State(src string) ImageState
type NumLine ¶
Numbered is a step's change as its lines, each with the file's line number when the change says where it is ("@@ -old +new @@" before its lines; hover-agents writes one only when it knows): a removed line has its old number, the others their new one. A line with Gap set is the gap between two parts of the change. With no header, no numbers.
type Painter ¶
type Painter struct {
// Images is shared with the thread (it lays images out by their size).
Images *Images
// Frames counts painted frames, for the benchmark.
Frames uint64
// Time is in seconds, for the live step's shimmer (a 2 s loop, as `@keyframes flow`).
Time float32
// Hover is the scrollbar whose thumb is under the pointer (it darkens).
Hover BarID
HasHover bool
// contains filtered or unexported fields
}
Painter paints the visible part of a thread into a pixel buffer: shapes, selection, glyphs (outlines, unhinted like Chromium's DirectWrite path), decorations, images and diagrams. Only the viewport is painted, and glyph masks, decoded images and rasterised diagrams are cached, so a scroll or a streamed chunk costs one viewport of drawing.
func NewPainter ¶
NewPainter makes a painter; sh gives a flowchart's text its fonts.
func (*Painter) Bar ¶
Bar draws a scrollbar (in CSS px of the buffer): the track, the arrow buttons and the 6 px thumb. radius rounds the track's corners (top left, top right, bottom right, bottom left) where the box's border does.
type Pos ¶
type Pos struct {
Section, Text, Byte int
}
Pos is a place in the text: a section, a text box in it, and a byte of the box.
type Scroller ¶
type Scroller struct {
// Clip is where the content shows (x, y, w, h): the padding box less the bar, which sits just under it.
Clip Rect4
// Content is the scrollable width.
Content float32
// Shapes scroll with the content (a table's header fill and row rules).
Shapes []Shape
}
Scroller is a box with `overflow: auto` that is wider inside than out (a code block, a table): its content scrolls sideways under a thin scrollbar along its bottom.
type Section ¶
type Section struct {
Y, H float32
Frag *Frag
// AnswerTok is where the answer's copy tokens start (a select-all inside `.ans`).
AnswerTok int
HasAnswerTok bool
// AnswerAt is where the answer's texts, shapes and scrolling boxes start (it fades in on its own).
AnswerAt [3]int
HasAnswerAt bool
// Images are the answer's images, and stale says one of them changed since it was laid out.
Images []string
// contains filtered or unexported fields
}
Section is one turn laid out.
type Shape ¶
type Shape struct {
Kind ShapeKind
X, Y, W, H float32
Radius [4]float32
Fill Rgba
Stroke Rgba
SW float32
Src string // Image: the source; Svg: the document
Cover bool // Image: object-fit: cover (a prompt's thumbnails)
Pts [][2]float32
}
Shape is something drawn. A Rect has Radius per corner, a Fill (transparent: none) and a border of width SW in Stroke.
type ShapeKind ¶
type ShapeKind uint8
const ( ShapeRect ShapeKind = iota ShapeImage // ShapeLine is an open polyline (Fill is its colour, SW its width). ShapeLine ShapeSvg // ShapeBroken is Chromium's broken-image icon (14 x 16), at the top left of an image that isn't there. ShapeBroken // ShapeGlow: X, Y the centre and W the radius. ShapeGlow )
type Smooth ¶
type Smooth struct {
// contains filtered or unexported fields
}
Smooth is a user scroll's animation, as cc's ScrollOffsetAnimationCurve animates a wheel, arrow or track scroll: ease-in-out (0.42, 0, 0.58, 1) over 6 to 12 frames at 60 Hz, shorter the further it goes (kInverseDelta: 12 frames up to 120 px, 6 from 480 px). A new scroll during one retargets it, keeping its speed (UpdateTarget). Times are seconds from any fixed start.
type Step ¶
type Step struct {
Kind StepIcon
Verb string
Name, Dir string
Cmd string
Status string
Add, Del int
Diff, Out string
Exit int
HasExit bool
Ms float64
HasMs bool
// Tag is the demo's own tag ("81 passed"): a check and the words.
Tag string
// Lead is what the agent said just before this step, shown above it.
Lead string
}
Step is one step as the host sends it (KiroPage.Row): its kind's icon, a verb, and the file (name and folder) or the command it was about, with the change it made or what the command printed, and how it went. A string field is empty when the host sent none.
type StepIcon ¶
type StepIcon uint8
StepIcon is a step's icon (main.js ICON): the host sends one of these per step. Thought is the reasoning the tool exposed; Agent a subagent it started (OpenCode's task tool).
type Tail ¶
type Tail uint8
Tail is what a selection that runs on past its last box takes in after it.
type TextBox ¶
type TextBox struct {
Layout *text.Layout
X, Y float32
// Text is the text as laid out, for copying (inline boxes are not in it). Empty for
// drawn-only text (list markers, the code block's language tag, an ellipsis).
Text string
Links []Link
// Clip is `overflow: auto` of the box it sits in, in the same coordinates as X, Y.
Clip *Rect4
// Shimmer: the live step's moving highlight (`.work .on` in page.html).
Shimmer bool
// Cell: a table cell: a double or triple click stays inside it.
Cell bool
// Scr is the box that scrolls it sideways: an index into Frag.Scrollers plus one (0: none).
Scr int
}
TextBox is a laid-out paragraph at a place.
type Thread ¶
type Thread struct {
Width float32
Who string
Color Rgba
Sections []Section
Height float32
// Selection is the anchor and focus; Tail is what it takes in after its last box.
Sel [2]Pos
HasSel bool
Tail Tail
// ImageState and ImageRule: what the layout knows of an image, and where one may load from.
ImageState func(string) ImageState
ImageRule md.ImageFn
// Session is the session shown (its id in the state message), for stepsUser.
Session uint64
HideSteps bool
// Tool is the tool, for its logo over each answer.
Tool string
// Copied is the code block just copied (its button says "Copied" a moment).
Copied *string
// ViewH is the thread's visible height.
ViewH float32
// Relayouts counts section layouts made since the thread was created (for the tests and the benchmark).
Relayouts int
// HScroll is how far each sideways-scrolling box is scrolled, by (section, scroller). A
// section laid out again starts at 0, as the page's re-rendered answer does.
HScroll map[[2]int]float32
// Fresh is `.ans.fresh`: the section whose answer just arrived, and when (the painter's clock).
FreshSection int
FreshAt float32
HasFresh bool
// ExtraBottom is room kept under the last turn (the reply circle sits over the thread's corner).
ExtraBottom float32
// Ticking: a command is running: its time counts, so the app lays the thread out again each second.
Ticking bool
// contains filtered or unexported fields
}
Thread is the whole chat, laid out.
func (*Thread) AccessibleBlocks ¶
func (*Thread) AnswerText ¶
AnswerText is a select-all inside turn i's answer (`.ans`), as the page copies it.
func (*Thread) Document ¶
Document lays src out as Markdown on its own, the way an answer is, for a page outside the chat (the desk's pull request description): one section with no prompt, name or buttons. width is the text's; the painter adds the thread's side padding on each side, so paint it width + 24 wide.
func (*Thread) Hit ¶
Hit finds what is under a point in thread coordinates (y from the thread's top).
func (*Thread) ImageChanged ¶
ImageChanged: an image arrived (or failed): the sections that show it are laid out again on the next Set. It returns whether any does.
func (*Thread) OpenDiff ¶
OpenDiff: a file under the answer was clicked: the timeline opens on its change. It returns where its row is now, in thread coordinates.
func (*Thread) ParagraphAt ¶
ParagraphAt is the paragraph a triple click at caret p selects: the line between hard breaks (a <br>, or a newline in code), and the break that ends it. The last line of a block takes the block's end; a table cell is selected alone.
func (*Thread) Select ¶
Select makes a selection from the anchor to the focus (none when they are the same).
func (*Thread) SelectAll ¶
func (th *Thread) SelectAll()
SelectAll selects everything, as a select-all over the thread does.
func (*Thread) SelectUnits ¶
SelectUnits selects a unit, or (after a double or triple click, while dragging) the anchor's unit grown by whole units to the one at focus, as Chromium extends by granularity.
func (*Thread) SelectedIn ¶
SelectedIn is the selected bytes of one text box, if any.
func (*Thread) SelectedText ¶
SelectedText is the selected text as the page would copy it (see Tok).
func (*Thread) SelectionRects ¶
SelectionRects are the selection's rectangles for one text box, in its own coordinates.
func (*Thread) Set ¶
Set lays the thread out for the drawer's width, re-using every section whose turn, width and step-list state haven't changed.
func (*Thread) SetCopied ¶
SetCopied: a code block's Copy was clicked: its button says so until the copy is done with.
func (*Thread) ToggleFlag ¶
ToggleFlag flips a step's switch (a long change shown whole, more subagents, one's result).
func (*Thread) ToggleGroup ¶
ToggleGroup: a click on a group's header: it opens or folds, and stays that way.
func (*Thread) ToggleStep ¶
ToggleStep: a click on a step with a change or output: its block opens or folds.
func (*Thread) ToggleSteps ¶
ToggleSteps opens or folds all of turn i's step groups, as its first one goes.
func (*Thread) TrailingSpace ¶
TrailingSpace: WebView2 (Windows editing behaviour) also selects the spaces after a double-clicked word, up to the next non-space or line break, inside the block.
func (*Thread) UseImages ¶
UseImages lays images out by their state in a shared cache (the painter's).
func (*Thread) WordAt ¶
WordAt is the word a double click at caret p selects. The caret is the boundary nearest the pointer, and the word is the one that starts there when it is on a boundary (Chromium's word granularity), so the right half of a word's last letter selects what follows it; but at the end of a soft-wrapped line it is the word before (ChooseWordSide in Blink's selection_adjuster.cc). At a block's end that is the break to the next block; a table cell selects nothing there.
type Tok ¶
Tok is what a copy is made of, in document order: the page's selection serialiser (Chromium's) adds newlines at block edges, and this reproduces what it gives for the blocks md.js writes. The rules were read off the real page (tests/golden/copy.json and the block-pair table in port/phase1/REPORT.md).
type TokKind ¶
type TokKind uint8
TokKind is a piece of what a copy is made of.
const ( // TokText is a selectable text box (N is an index into Frag.Texts). TokText TokKind = iota // TokReq: at least N newlines before the next text: the edge of a block. TokReq // TokLit: characters between two boxes of one block (a table's tab, a <br> before an image). TokLit // TokImg: a paragraph that is only an image (display: block, no text). TokImg // TokHr is a rule. TokHr // TokVirt is text that is copied but drawn by something else (a diagram's labels). TokVirt // TokTableEnd is a table's end: one newline, and Chromium writes it even when the table is last. TokTableEnd )
type Turn ¶
type Turn struct {
Prompt string
// Images are the prompt's pasted images (URLs the painter's loader can fetch).
Images []string
Queued bool
Steps []Step
// Took is how long it worked ("3m 07s"); TookMs the same in ms.
Took string
TookMs float64
HasTookMs bool
// Credits is what the turn cost ("0.09 credits"), when the tool says.
Credits string
Stage Stage
// Live: the turn running now (working, or waiting on the user).
Live bool
// Clock is how long the live turn has gone ("0:12").
Clock string
// When the prompt was sent (.me .when).
When string
Status string
Answer string
// Waiting: the live turn waits on the user (a question, a permission).
Waiting bool
// Stopping: asked to stop or pause, and the tool hasn't said it has yet.
Stopping bool
// Restore and Again: its acts row offers Restore (the chat and the folder back to just
// after this answer) and Try again (the folder back to before this message, which goes
// again): a checkpoint was kept there and nothing runs.
Restore, Again bool
}
Turn is one turn as the office shows it.