mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-30 04:22:51 +00:00
* feat(desktop): mention pills for files, folders, and skills
@ -mentions now insert as inline pill atoms in the ProseMirror composer
(desktop; the web textarea keeps plain-path behavior):
- schema gains a mention inline atom (file/folder/skill) rendered by a
NodeView with the same glyph as the menu row; the doc model's
serialization, offset mapping, and clipboard contract all go through
the node's leafText, so the wire payload stays plain text — pills
serialize as Markdown links: [name](path), [name](path/) for folders,
[name](kimi-code://skill/<name>) for skills
- the mention menu gains a Skills section (instant local filter) below
the Files section; useMentionMenu takes optional skills + insertMention
deps, so the web textarea path is unchanged
- send semantics: a message with exactly one skill pill activates that
skill (the pill form of /skill:<name>, surrounding text becomes args,
attachments ride along); two or more skill pills degrade to plain
references
- pill insertion replaces the @token via a dedicated transaction that
adds a separating trailing space and leaves the caret after the pill
* fix(desktop): hide the phantom line after a paragraph-ending pill
PM appends a trailing <br> when a paragraph ends with an inline atom
(a caret target after the pill), and its stock stylesheet has no rule
for it — rendered, it reads as a forced extra line after the pill.
Hide it except when it is the paragraph's only child (the empty-state
placeholder line still needs it).
* feat(desktop): instant @ menu, stable list while searching, pill-adjacent tokens
- a bare '@' now opens the mention menu immediately with the daemon's
empty-query listing (workspace root entries) instead of staying closed
until the query has content; Esc still dismisses
- typing in the open menu no longer swaps the candidate list for a
full-area 'searching' note: rows stay visible and a corner spinner
marks the in-flight search (the note only remains when the list is
empty); same for the in-flight state of a new keystroke
- the @token scan no longer crosses into a mention pill's serialized
form: the editor reports the caret's inline-run start and the scan is
bounded there, so '@' typed right after a pill opens the menu
- folder detection uses the daemon's explicit kind (now plumbed through
FileItem) with the trailing-slash convention as fallback — icons and
pill serialization follow it
* fix: trigger the mention menu on the full-width @ too
Chinese IMEs produce U+FF20 (@) for the @ key, which the token scan
rejected — the menu never opened for IME-typed mentions. Accept both
forms; the replacement range covers the full-width char as before.
* fix(desktop): the @ menu never opened after a keystroke
inlineTextRunStart assumed $pos.textOffset is the caret's offset within
the current text node — but ProseMirror reports 0 at a text node's END
boundary ('between nodes'), which is exactly where the caret lands after
every keystroke. The lower bound therefore equalled the caret, the token
scan saw nothing, and the menu never opened on real typing (paste-driven
flows happened to work). Extract the logic as inlineRunStartOffset and
handle the boundary via nodeBefore; cover all caret placements with
pure tests (no DOM needed).
* fix(desktop): give the caret a home right after a paragraph-trailing pill
When a paragraph ends with a mention atom, PM appends a separator img
plus a trailing <br>, and Chromium draws the caret on the br's phantom
line below the pill (hiding the br instead leaves the caret homeless).
Add a decoration plugin that drops a zero-width caret-anchor span right
after a paragraph-trailing pill, and keep the br hidden in that state —
the caret now sits on the pill's own line.
* feat: render mention links as pills in sent messages too
One pill vocabulary in both directions: the Markdown layer now decorates
every workspace link (plain paths, trailing-slash folders, and
kimi-code://skill/ links) with the same classes and kind glyph the
composer's atom uses — the pill styles move to app-ui's global sheet.
Classification is the pure classifyMentionHref (unit-tested); files open
the preview on click, folders and skills stay inert until they have a
navigation target. app-markdown gains a (one-way, acyclic) dependency on
app-client for the shared helpers.
* docs: spec the mention pill as §05 Rich Text Messages
Promote the pill from a Composer paragraph to its own design-system
chapter: the shared two-surface concept (composer atom + decorated
message link), the visual recipe, the kinds/wire-forms table, the
behavior contract (bare-@ listing, sections, stable list while
searching, atom deletion + caret anchor, single-skill activation), and
the implementation map. §04 keeps a pointer; later sections renumber
(nav + banners + sec-nums) on both ends.
* fix(desktop): keep a real text node after an inserted pill
The caret-after-trailing-pill problem, solved the way Slate-style
editors do it: give the caret a real text home instead of letting the
paragraph end in an atom (which is what forced PM's trailing <br> and
dropped the caret onto a phantom line). buildMentionInsertion now
appends the separating space also at end-of-text, and the zero-width
caret-anchor decoration stays as the safety net for when the user
deletes that space. Also registers a dev-only window.__composerEditors
handle for CDP verification.
* fix: give skill mentions their own glyph
The skill pill used the 'sparkles' registry slot, which is backed by the
task.svg glyph — the Swarm/sub-agent icon. Register ri/sparkling-line as
'sparkling' (an actual sparkles glyph) and use it for skill pills and
skill menu rows instead.
* fix: restyle mention pills as plain text, not filled chips
Per design feedback: no background, no radius, no padding — the mention
is body text set in --weight-ui-strong on --color-text-muted, baseline-
flush with the surrounding text, with the muted kind glyph leading.
States follow the same idiom: editor node-selection deepens the ink,
message hover underlines the clickable file pill. §05 spec text updated
on both ends.
* fix: tighten the mention pill's icon-to-label gap
* fix: mention pill icon — match text height, heavier glyph, selectable
- size moves to the --p-ic-sm token (14px, level with the 14px text)
- registry glyphs are fill-based, so a hairline currentColor stroke adds
the perceived weight
- ::selection paints the icon span, so a text selection crossing the
pill no longer leaves the glyph unselected
* fix(desktop): revive mention pills from their link form on load
Drafts are the serialized Markdown-link text, so a refresh used to drop
the pills. textToDoc now parses our own link forms back into mention
atoms on the editor's load paths (draft restore, history recall, queue
reload), making the serialization a true round trip; the paste path
keeps pills off. Also escapes '<' in angle-bracket destinations so the
parse is faithful.
* fix(desktop): revive pills on paste too
Copying a pill puts its Markdown-link form on the clipboard, but the
paste path deliberately skipped mention parsing, so a pasted pill came
back as literal text. parseClipboardText now revives mention links, so
copy-paste round-trips like load-path revival does.
* fix: paint the mention icon on selection for real
The earlier rule targeted only the icon span; Chromium doesn't selection-
paint an svg inside it. Cover the pill and every descendant instead.
* feat: render mention pills in user and queue bubbles too
User/queue bubbles render verbatim text (never Markdown), so serialized
mention links arrived as literal [name](path) text. A new pillifyMentions
pass (ChatPane register/hooks) revives them into pill elements built by
the same shared builder as the composer's NodeView — one pill vocabulary
across menu, editor, and every message surface. File pills in bubbles
open the preview on click; folder/skill stay inert.
* fix: paint pill selection via a class, not ::selection
Browsers paint the selection highlight over text and <img>, but not over
an inline <svg> (csswg-drafts#5395) — the pill's glyph always escaped
the highlight. mentionSelectionSync watches document selectionchange and
toggles .pill-in-selection on the pills the range actually covers
(Range.intersectsNode); the stylesheet paints the wash on that class.
Wired into the composer and the chat stream on both ends. Verified live:
class applies when covered, clears on collapse.
* feat: show the skill as a pill in the activation card too
The activation card's title name now renders as the same skill pill the
editor shows (i18n-t slot), and carving the pill out of the args merges
the seam whitespace so the displayed/sent args read naturally.
* feat(composer): mention pill hover tooltip and click-to-open
One document-level mentionTooltip singleton serves every pill surface
(composer NodeViews, pillified bubbles, Markdown anchors) since pills
are raw DOM a Vue wrapper can't reach. File/folder hovers show the
full path (fixed max width, forced wrap at segment boundaries, muted
separators, bold basename); skill hovers show a card with the name,
an open button, and the description. Skill resolution rides the wire
skill descriptor's path through AppSkill.path, and clicking a skill
pill (or the card's open button) in messages opens its SKILL.md in
the preview panel. Composer pills keep pure editing semantics: I-beam
cursor, no underline, click places the caret. Native title tooltips
are removed wherever a pill appears.
* feat(composer): mention pill name truncation and missing-target marking
Over-long pill labels middle-ellipsis at 32 chars, keeping the base
name's head and the whole extension (the full name stays in the data
attributes, the full path on the tooltip). Hovering a file/folder pill
fires a one-byte existence probe with a spinner at the tooltip's tail;
a definitive fs.path_not_found fades every pill referencing the path
and strikes it through, while clicks stay enabled regardless. Only
confirmed-existing verdicts are cached (scoped to the session), so a
recreated file recovers on the next hover and transient daemon
failures can never strike a pill by mistake.
* fix(composer): give mention pills a small horizontal padding
CJK autospacing cannot cross element boundaries, so a pill abutting
Chinese text rendered flush against it. A 2px padding-inline on every
pill separates the mention from its surrounding text on all surfaces.
* feat(chat): render single-skill-pill messages verbatim, no activation card
A message carrying exactly one skill pill still activates the skill via
the slash-command channel, but the pill is no longer carved out of the
text: the full serialized text (the pill as its mention link) becomes
the args, so the sent bubble shows the original message with the skill
revived as an inline pill where the user typed it. The 'Activated
skill' card header is gone — the message content speaks for itself.
Skill activations also count as editable turns again: the args are the
full original text, so edit-and-resend revives the pill and
re-activates. Two-or-more skill pills still degrade to plain
references, and plugin command cards are unchanged.
* refactor(composer): one file glyph for every file mention
Drop the per-extension code/doc/image icon variants: every file pill
and mention-menu file row now uses the single folded-corner file glyph
(no lines on the page), folders keep the one folder glyph.
* feat(composer): copy-path button on file/folder mention tooltips
The path tooltip is now a flex row: the wrapping path on the left and
a copy button pinned top-right. A successful copy swaps the icon for a
check for about a second; clipboard failures stay silent and the
tooltip stays open. The button's aria-label is localized via a new
mention.copyPath string.
* fix(composer): harden mention link parsing and serialization
Address review findings in composerTextDoc:
- copying a lone pill via NodeSelection serialized to an empty string —
top-level leaf mention nodes now emit their link form directly;
- Markdown image syntax () and escaped brackets no longer
revive into mention pills;
- parseMentionLinks rescanned the whole suffix per '[', degrading to
O(n^2) on bracket-heavy text — it now scans linearly with proven
dead-start skipping (fuzz-verified equivalent to the naive scan);
- hrefs with any URI scheme (ftp:, vscode:, …) no longer classify as
workspace file mentions; Windows drive paths still do;
- a revived skill pill takes its identity from the decoded link target
instead of the display label, so [发布](kimi-code://skill/deploy)
activates 'deploy';
- folder classification no longer strips '#'/ '?' — folder names may
legally contain them, and stripping ate the trailing slash.
* fix(markdown): open the real filesystem path behind mention links
Address review findings in the Markdown link pass:
- mention hrefs are pure paths — stop stripping at '#'/ '?' so file
names containing them survive (stripFragmentAndQuery removed);
- percent-encoded hrefs (%20, UTF-8 sequences) are decoded before
openFile and before the tooltip probe sees them, via the new pure
mentionHrefToPath helper (malformed sequences fall back to the raw
href);
- hosts without an openFile prop (UpdateIndicator, QuestionCard) no
longer get their links killed by an unconditional preventDefault —
default navigation passes through; skill links still always prevent
it (the mention tooltip routes them).
* fix(composer): mention menu interaction and async race fixes
Address review findings in the @ menu and composer:
- keyboard no longer locks while a search is in flight: arrows,
Enter/Tab and Escape work whenever the menu is open (web composer
had the same gate);
- changing the query clears stale file candidates immediately instead
of leaving the old query's rows clickable;
- the highlighted row is restored by identity when async file results
arrive, not by a numeric index that shifts under it;
- an incrementing sequence guard makes only the latest search apply
its results and clear loading;
- skills arriving asynchronously now refresh an already-open menu;
- Escape and select both cancel the pending debounced search, so a
closed menu can't reopen itself into a stuck loading state;
- dropping folders onto the composer inserts through a real editor
transaction (new insertTextAt API) instead of rebuilding the whole
document, preserving the undo stack and existing pills.
* fix(composer): mention tooltip polish, pill a11y, and selection sync perf
Address review findings:
- tooltip padding and spinner animation now use design tokens
(var(--space-1) var(--space-2), new --duration-spin) instead of
hardcoded values;
- a hidden tooltip is marked inert so its buttons leave the tab order;
- path probes capture the session scope at start and discard verdicts
that arrive after a session switch, and confirmed-existing verdicts
now expire after 30s so a later-deleted file does get struck;
- message-side file pills are keyboard reachable (tabindex, role,
Enter/Space) with the standard focus ring;
- mentionSelectionSync no longer scans every pill on each caret move —
it only clears the previously marked set unless a non-collapsed
selection intersects the watched root;
- design spec and native-todos divergence notes updated to the real
padding / paste-revive / skill-click behavior.
* fix(chat): keep the activation card for slash-typed skill activations
Follow-up review on the verbatim skill-message change: the card removal
was unconditional, but a /skill-typed activation's bare args carry no
mention pill — its skill identity would vanish from the transcript.
skillActivationHasPill() checks whether the args revive the matching
skill pill: pill-composed activations show just the verbatim message,
slash-typed ones keep the identity card. The same guard keeps edit &
resend away from bare-args activations (resending those would degrade
to a plain prompt instead of re-activating).
Also documents the mention tooltip as a structural exception to the
component-primitive rule (its anchors are ProseMirror NodeViews and
pillified spans a Vue wrapper can't reach) and gives its open/copy
buttons the standard focus ring the Button primitive would enforce.
* fix(composer): lossless mention paths for literal percent signs and nested brackets
Address review findings:
- a filename containing a literal percent triplet (report%20final.md)
was decoded as if the renderer had encoded it, opening the wrong
file. The serializer now escapes '%' as '%25' in link destinations —
a valid triplet the renderer preserves, so the message-side single
decodeURIComponent restores the true name (a literal %2F no longer
becomes a path separator);
- an unclosed '[' followed by a real mention (prefix [unfinished
[README](README.md)) paired the later ']' with the earlier bracket
and swallowed the prefix into the pill label. An unescaped inner '['
now invalidates the outer candidate and scanning resumes from the
inner bracket, still in linear time.
* fix(composer): clamp the mention tooltip on narrow viewports and make message skill pills keyboard-reachable
Address review findings:
- below ~336px viewport width the 320px bubble out-measured the
viewport and the left clamp could go negative, cropping the path
offscreen — the bubble now caps at innerWidth minus margins and the
clamp bounds can no longer invert;
- message-side skill pills open their SKILL.md on mouse click but had
no keyboard path — the singleton now routes Enter/Space on skill
pills through the same resolve-and-open logic (capture phase), and
both pillified bubbles and the legacy activation card expose
tabindex/role so the pills are focusable with the standard ring.
* fix(composer): invalidate in-flight mention searches earlier and probe paths before the first message
Address review findings:
- the search sequence only incremented when the debounced runSearch
started, so a same-query request could re-validate a stale in-flight
response inside the debounce window — update() now bumps the
sequence as soon as the token changes;
- probing a mention path before the session's first message returned
an unconditional 'exists' and cached it. With no active session the
probe now resolves the workspace root and reads via the daemon's
global fs:content instead, keeping the definitive-not-found-only
verdict semantics; the probe scope falls back to the workspace id so
onboarding verdicts never leak across workspaces.
* refactor(app-client): group the composer editor cluster under lib/composer/
The ProseMirror document model and editor surface (composerTextDoc,
composerEditor), the per-session state cache, the shared TextFieldLike
abstraction, and the whole mention content type (icons, pill builder,
message-side pillify, selection painting, hover tooltip) move from the
flat lib/ into lib/composer/, where upcoming rich-text content types
will join. lib/index.ts keeps re-exporting the cluster, so consumer
imports are unchanged; only in-package relative imports were updated.
No behavior change.
* refactor(composer): parse mention links with micromark instead of a hand-rolled scanner
The hand-written scanner kept re-discovering tokenizer problems (O(n^2)
rescanning, image syntax reviving, inner brackets swallowing prefixes).
Replace it with micromark's event stream, construct-disabled so only
link-related constructs parse: emphasis, autolinks, definitions, HTML,
block structure, and friends are all off — only inline links, image
labels (rejected wholesale), character escapes, and code constructs
stay on. Rejects (images, titles, empty parts, non-mention schemes)
happen in our pipeline; malformed-input behavior is the library's
problem now. Dest decoding keeps the wire format's own inverse
(backslash + %25) applied to spans cut from the original text, so
micromark's normalization never touches it.
Also ships the wire-format spec (docs/specs) this parser is validated
against, and one serializer fix the differential fuzz caught: skill
names with unbalanced parens now percent-encode parens in the link
destination, or CommonMark would refuse to revive them.
Fuzz: 100k inputs vs the retired scanner — 375 differences, all in the
documented hand-typed-only buckets (code shielding, titles, balanced
parens, …); round-trip fidelity for serialized docs is identical.
* fix(composer): mention menu skill refresh timing, gate restore text, queue pill nesting
Address review findings:
- skills arriving during the file-search debounce window no longer get
dropped: a dismissed flag separates 'closed on purpose' from 'not
open yet', and the watcher refreshes candidates whenever the menu
was not explicitly dismissed — without ever reopening it;
- a gated single-skill-pill send restored the synthesized /skill:<name>
command line into the composer, so resending stacked another prefix
into the args — the command payload now carries restoreText with the
original message, and the gates prefer it when handing text back;
- queued prompts are one big click-to-edit button, so their pills no
longer get button semantics of their own (no nested buttons): the
queue body keeps the click, pills there stay display-only, and the
tooltip's skill-open routing skips them too.
* fix(markdown): act on the fragment-free path, take skill identity from the link target
Address review findings:
- ordinary chat links with an in-page anchor or query
([Usage](README.md#usage)) were opened as a literal path and 404'd —
action sites (click-to-open, existence probe) now cut the first
unencoded #/? tail via the shared mentionActionPath helper, while
display, tooltip, copy, and the wire format keep the full decoded
path;
- a skill pill on the Markdown surface took its identity from the link
label, so [发布](kimi-code://skill/deploy) resolved nothing — the
tooltip and click now key off the decoded link target, same as the
composer revive path;
- inert folder pills render as focusable anchors that do nothing on
Enter — their href is removed so they leave the link tab order;
- pills inside FilePreview kept the raw relative href in
data-mention-path, so the tooltip probed (and could wrongly strike)
a workspace-root path — Markdown accepts a resolveMentionPath prop
and FilePreview passes its click-time directory resolution through.
* fix(composer): close the remaining round-trip gaps in mention parsing
Address review findings:
- protocol-relative URLs ([site](//example.com/path)) misclassified as
workspace files — excluded before the scheme check;
- filenames containing '<'/'>' serialized to a bare destination that
not every parser reads back (a leading '<' is outright illegal), so
our own pills failed to revive — the angle form now triggers on
whitespace OR angle characters, and a filename corpus test pins the
round-trip invariant as the arbiter for all future serialization
questions;
- code constructs (codeText/codeFenced/codeIndented) shielded links
from the wire parser, but the editor can legitimately hold a real
mention atom between code delimiters — the round-trip invariant
outranks literal purity for hand-typed text, so the wire parser now
recognizes mention links in every context (assistant Markdown, a
real renderer, is unaffected).
* fix(composer): keyboard access into the mention tooltip and grapheme-safe truncation
Address review findings:
- the any-key dismiss made the interactive tooltip inert before Tab
could move focus into its buttons — Escape still always dismisses,
Tab never does, other keys only dismiss outside the bubble, and
focusin/focusout take over keeping/closing once focus actually
moves;
- truncateMentionName sliced UTF-16 units and could halve a surrogate
pair at the truncation boundary, rendering a replacement char — the
budget now counts grapheme clusters via Intl.Segmenter, so emoji and
ZWJ sequences are never cut in half.
* fix(composer): exact action paths, visible-workspace probe fallback, and tooltip timer races
Address review findings:
- FilePreview's display-path resolver stripped '#'/'?' from an
already-decoded path, mangling literal-# filenames in the pill's
dataset — the resolver splits into a display form (dir-resolution,
no strip) and an action form (strip, then resolve); Markdown also
stamps data-mention-action-path computed from the RAW href, so the
tooltip probe can tell a fragment tail from a filename character;
- probing before a workspace is explicitly picked fell back to
always-exists — the fresh-empty-workspace case now falls back to the
first visible workspace, the same chain createGoal uses;
- a pill unmounted during the 150ms show delay produced a bubble
stranded at (0,0) — detached anchors are discarded before show();
- re-entering the same pill inside the hide grace no longer lets the
pending hide fire under the cursor — the timer is cancelled on
same-anchor re-entry.
* fix(composer): focus-triggered tooltip, actionable-only skill pills, external-path probing
Address review findings:
- keyboard-only users got no tooltip at all — the singleton now opens
the bubble on pill focusin and closes on focusout, mirroring the
mouse path, so the interactive buttons are reachable without a
mouse;
- an unresolvable skill (uninstalled, or a host that can't open files)
was a permanently dead button: activation now only consumes the
event when the skill resolves to a path and the host can open it,
and the pill is degraded at tooltip time (tabindex/role stripped,
pointer/underline affordance removed via .mention-inert);
- absolute paths outside the workspace went through the session fs
endpoints, where the out-of-gate error masqueraded as 'exists' — the
probe now routes absolute paths through the daemon's global
fs:content, like the preview does.
* feat(composer): denser menu rows and UI polish for the mention surfaces
- the path tooltip's copy button now sits the same --space-1 from the
top border as from the right border (it was optically off-corner);
- menu row block padding slims 5.5px → 3.5px (shared by slash /
mention / add menus) and the mention menu's scrollport cap rises
208px → 296px, so roughly ten rows show instead of six.
* feat(composer): slim menu rows further (3.5px -> 1.5px block padding)
* feat(composer): plain 12px corners for the composer menus, concentric 6px rows
The slash / mention / add menus drop the composer card's 32px
superellipse geometry: the frame is a plain --radius-lg corner (no
corner-shape), and --radius-menu-row resolves to --radius-sm so the
row caps stay concentric with the frame (12px - 6px hug). The 0.5px
transparent-border corner-shape workaround and its padding
compensation go with it; effective row geometry is unchanged. The
composer card itself keeps its own radius and shape.
* fix(composer): keep mention candidates on screen during re-search, dimmed
Retyping the @-query cleared the file section synchronously, so every
keystroke collapsed the menu into an empty 'searching' state for the
debounce + RPC round trip. The rows now stay visible with a stale
flag (dimmed at 0.55 opacity with a fade) until the new results land
— the menu never flashes empty. Selecting a stale row still inserts
its mention against the live token, so nothing about the async
correctness changes (the searchSeq guard is untouched).
* feat(composer): densify mention menu rows — 13px text, 14px icons, tighter paddings
Item text is one 13px size for both name and meta (riding the
font-shift scale; they now differ only in ink and weight, name keeps
500 + full color, meta keeps muted). Row glyphs pin to 14px (they were
locked at 13px), inline row padding drops 10px to 8px, and the menu
frame's own padding slims 6/12 to 4/8 for a more compact surface.
Mention menu only — the slash and add menus keep their current look.
* feat(composer): bump mention menu frame padding to 6/8 (4/8 read too tight)
* feat(composer): show only the containing directory in mention menu rows
The meta text after a file/folder name was the full path — noisy and
mostly ellipsis-truncated. It now shows just the containing directory
(apps/desktop for apps/desktop/package.json); root-level entries show
nothing, since there is no directory to disambiguate.
* feat(composer): fixed gap between mention row name and directory meta
The name's 80px min-width made the name-to-meta distance vary with
name length (column-aligned for short names, flush for long ones). The
name now hugs its content, so the meta always follows at the row's
fixed 7px flex gap.
* feat(composer): land mention rows 4px inside the frame edge (hug -6 + 10px frame padding)
Restore the -6px hug outreach (the scrollport clip trick needs it) and
pad the frame 10px inline instead, so the visible row inset is exactly
4px (6 + 4) instead of the 8.5px the margin-less version had.
* fix(composer): hide the UA default button border on add-menu rows
The corner-shape cleanup removed the 0.5px transparent border from
.am-row, but it is a <button> — the UA default border immediately
showed through as a thick outline around every row.
* feat(composer): align mention menu frame padding with the slash/add menus
The tighter frame experiments (3px/4px row inset) read worse in
practice — the mention frame goes back to the shared 6/12 padding, so
all three composer menus hug their rows 6px from the frame edge. The
item-level changes (13px text, 14px icons, 8px inline padding,
directory-only meta, fixed name gap) stay.
* feat(composer): slow the mention menu stale fade (120ms -> 260ms)
The whole-menu dim on re-search read as a jarring flash at the fast
duration; the slow token eases it into a settle.
* fix(composer): address the latest review round
- tooltip vertical clamp could invert on very tall bubbles (high zoom /
long wrapped paths), pushing the bubble above the viewport — the
upper bound is floored at the margin like the horizontal one;
- probe spinner geometry (10px box, -1px baseline) now derives from
spacing tokens and an em-based offset;
- 32-grapheme labels can still outgrow narrow bubbles (CJK/full-width
glyphs), so the pill label also caps by inline size and ellipsizes;
- copy-path reuses copyTextToClipboard (execCommand fallback for
plain-HTTP web) and only confirms real copies;
- stale file rows are no longer selectable (Enter/Tab/click during a
re-search can't insert an old query's path; skills stay selectable);
- composer-wire pills act on their exact dataset path everywhere —
fragment/query trimming is confined to raw Markdown hrefs (the
Markdown surface stamps data-mention-action-path for it), so literal
'#'-filenames open and probe correctly from user bubbles;
- a leading '#' in a destination is percent-encoded as %23, so real
files like #notes.md revive (classification now runs on the raw,
still-encoded destination; the two encoding layers cannot alias);
- FilePreview remounts Markdown on path change, so identical-content
files re-resolve mention paths against the new directory;
- native-todos paths updated for the lib/composer/ move.
* fix(composer): key path probes by action path, encode CR/LF in mention destinations
Address review findings:
- two pills can share a display path while meaning different files
([Usage](README.md#usage) vs a literal '#'-filename) — the probe
cache, inflight dedupe, and the missing strike are now all keyed by
the pill's ACTION path instead of its display path, so one verdict
no longer poisons or strikes the other meaning;
- POSIX allows newlines in filenames, but a literal CR/LF in the wire
text would split paragraphs and kill the link — destinations now
encode them reversibly as %0A / %0D (aliasing with the %25 layer
verified).
* fix(composer): use the --text-sm rung for mention menu text, restore skill pill semantics on resolve
Address review findings:
- the 13px mention-menu font size was a hardcoded base value instead of
a design token — it is now the existing --text-sm ladder rung
(calc(var(--ui-b2) - 1px)), so the menu follows the global font
scale like everything else;
- a skill pill degraded while the skill list was still loading stayed
inert forever — when a later hover/focus resolves the skill, the
button semantics (tabindex, role, affordance) are restored
symmetrically (queue-body pills excepted: their row IS the edit
button).
* fix(composer): queue single-skill-pill sends while busy, Tab into the tooltip, colon filenames
Address review findings:
- sending a single-skill-pill message into a RUNNING session fired
activateSkill immediately — a busy refusal after the composer had
already cleared lost the message and its attachments. The branch now
only activates while idle; busy sends fall through to the normal
queue (plain prompt on replay, same as multi-pill). A failed
activation also restores the original text (restoreText) as defense;
- Tab from a focused anchor pill now moves focus into the tooltip's
first button instead of skipping past it in the DOM order
(Shift+Tab and action-less bubbles keep the default order);
- POSIX filenames containing ':' ('notes:old.md', 'http:fixture')
matched the URI-scheme guard and could never revive — the first
colon is encoded as %3A in destinations (Windows drives exempt,
aliasing with the %25 layer verified), closing the last
own-serializer round-trip gap in this family.
* fix(composer): guard the late activation-failure restore, decode standard percent-encoding
Address review findings:
- a slow activateSkill failure restored the old text unconditionally —
a new draft typed in the meantime (or a session switch) got
clobbered, possibly writing the old session's content into another
one. The restore now only lands when the request's session is still
active AND the composer is still empty (new isComposerEmpty expose);
- hand-written percent-encoded local links ([文档](my%20file.md))
revived with the '%' left in the path, and re-serialization
compounded it to '%25' — silently rewriting the link's meaning.
unescapeLinkDest now decodes with decodeURIComponent (malformed
sequences keep the raw text), covering the serializer's own layers
and standard Markdown encoding in one step.
* chore: lockfile after origin/main merge (main's lockfile + micromark)
* fix(desktop): optional-call Composer isEmpty (exposed type may be undefined); lockfile with micromark
* fix(composer): explicit slash wins over skill-pill activation, report first-activation outcome
Address review findings:
- a message starting with a known slash command whose args held exactly
one skill pill (/compact [deploy](…)) was rewritten into a skill
activation before parseSlash ran, hijacking the chosen command. The
command is resolved first now; the single-skill-pill branch only
applies to plain messages (still idle-only);
- startSessionAndActivateSkill swallowed the activation result and
always returned the new session id, so a refused first activation
(persist failure, daemon busy) looked like success while the composer
had already cleared. It now returns { sessionId, activated }, and
both app shells restore the original text (restoreText) on
activated:false — guarded the same as the active-session path (same
session + still-empty composer). Web gains the same guarded restores
on both branches and the isComposerEmpty expose.
* fix(composer): use the --p-ic-sm token for mention menu icons
The menu's icon box and glyph were pinned at a hardcoded 14px — the
design-system icon scale already owns that rung (--p-ic-sm), so a
global icon-size adjustment now reaches the mention menu too.
* fix(composer): fully-idle gate for skill activation, folder href only where openFile exists, tooltip tokens
Address review findings:
- a single-skill-pill send activated directly even with a non-empty
queue (jumping the FIFO the normal submit preserves) or while
running-but-not-working (approval/question pending) — direct
activation now requires a fully idle session AND an empty queue;
- folder links on Markdown surfaces without an openFile handler
(UpdateIndicator, QuestionCard) had their href removed and became
dead static text — the inert-pill treatment now only applies where
the host can actually open files;
- the mention tooltip's geometry and delays are read from design
tokens via getComputedStyle (--space-1-5 / --space-2 / new
--duration-tooltip / --duration-fast), and the width cap moved into
CSS (new --p-mention-tip-w with a min() viewport clamp), so the
interaction follows the spacing/motion scale like everything else.
* fix(composer): literal folder-drop insertion, tab order out of the tooltip
Address review findings:
- a dropped folder path whose dirname merely looked like a mention link
('[archive](old)') was revived into a pill pointing at the wrong path
— insertTextAt now takes reviveMentions (default true) and folder
drops pass false, keeping the single-transaction undo behavior while
the path stays literal text;
- tabbing out of the tooltip (Tab on its last button, Shift+Tab on its
first) returns focus to the anchor pill and closes, instead of
continuing from the page end and skipping every control after the
anchor (the bubble is appended to document.body).
* fix(composer): stale-opacity token, no re-strip in preview, no skill-edit on web
Address review findings:
- the stale-row dim (opacity 0.55) was a hardcoded value in both menu
copies — it is now the semantic --opacity-stale token in the shared
sheet;
- FilePreview re-stripped '#'/'?' from paths Markdown.vue had already
normalized (strip on the raw href, then decode), so a literal
'#'-filename opened its prefix instead — the preview's open-file
forwarding now only does directory resolution, and the strip variant
is gone;
- web offered edit-and-resend on a desktop-sent single-skill-pill
activation, but the web textarea can neither revive the pill nor
re-activate it — the edit would silently degrade to a plain prompt
while the original turn is undone. canEditTurn now excludes skill
activations on web entirely (divergence documented in
native-todos.md; desktop keeps the pill-aware editing).
* fix(composer): single-decode skill names, workspace-scoped probes on empty session id
Address review findings:
- a skill name with a literal percent triplet (review%20draft) was
decoded twice on revive — unescapeLinkDest turned %2520 into %20 and
decodeSkillName finished it into a space, landing on a nonexistent
skill. The skill branch now hands the RAW destination to
decodeSkillName so exactly one decodeURIComponent happens;
- the no-session probe scope fell back to the workspace id only on
null, but activeSessionId normalizes to an empty string — two
sessionless workspaces shared the '' scope and poisoned each other's
verdicts. The fallback now uses ||, covering both null and ''.
* fix(composer): restore on failed session creation, encode the last round-trip breakers
Address review findings:
- a first-skill-pill send whose session CREATION failed returned
{ sessionId: null, activated: false }, but the restore guard compared
the empty-string active id with null and never fired — the cleared
message was lost. The new-session restore now fires on a failed
creation too (still on the same workspace + empty composer);
- a newline in the BASENAME broke the wire the same way as one in the
path — labels now encode CR/LF as %0A/%0D (and decode them back);
- paths with a leading '//' (POSIX or forward-slash UNC) read as
protocol-relative URLs to the classifier and could never revive —
the second slash is encoded as %2F;
- filenames containing '&' decoded as HTML character references in the
real Markdown renderer ('a&b.md' opened 'a&b.md') — '&' is now
encoded as %26 in destinations.
* docs(spec): label CR/LF and the %26/%2F destination layers
* fix(composer): label percent layer, structured skill name in command payloads
Address review findings:
- a literal '%0A'/'%0D' in a filename was decoded as a real newline in
the label on revive, rewriting the pill's name — labels now encode
'%' as '%25' FIRST (before the newline layer, so the two cannot
alias), same as the destination;
- a skill name containing spaces ('write goal') was synthesized into
the space-delimited '/skill:write goal …' command string, which the
handler split at the first space and activated 'write' instead. The
name now rides the command payload structured (skillName), on both
the single-pill path and the slash-menu path; the handler prefers it
over parsing.
* fix(composer): decode wire labels on the Markdown surface, pill-name span, stale-aware default highlight
Address review findings:
- a name with a literal '%' showed its encoded wire form (a%2520b.md)
in assistant Markdown / previews, because the decoration used the
raw link text — it now decodes the label with the composer's own
unescapeLinkText;
- Markdown pills kept the name as an anonymous text node, so the
shared max-width/ellipsis rules never applied and a long nowrap name
could stretch the column — the display name is now wrapped in
.mention-pill-name like the shared builder;
- after retyping, the default highlight landed on index 0 — a stale,
unselectable file row — making Enter look dead until the new search
returned. With fresh skill rows present the default now lands on the
first skill instead.
* fix(composer): structured args with structured skill names, interior #/? encoding, token feedback delay, keep image links
Address review findings:
- the structured skillName fixed the name but args still parsed from
the cmd string's first space, so a spaced skill name leaked its
suffix into the args — when skillName is present the args now come
from the original message (restoreText) or the exact
'/skill:<name>' head, never from string splitting;
- interior '#'/'?' in a path survived unencoded, so action sites on
Markdown surfaces stripped them as fragment/query and opened the
path prefix — every literal '#'/'?' in a destination now encodes as
%23/%3F (hand-written fragment links still strip, since theirs is
unencoded);
- the copy-success check reset used a hardcoded 1000ms — it now reads
--duration-flash via the same tokenPx path as the other delays;
- a clickable thumbnail ([](full.png)) was pillified
and its <img> subtree erased by the name span — links containing
images are now skipped by mention decoration.
* fix(composer): label metachar encoding, keep pills focusable while skills load
Address review findings:
- a name containing '&', '<', or '>' showed decoded forms in assistant
Markdown ('a&b.md' displayed 'a&b.md') — labels now encode those
inline-HTML metacharacters as %26/%3C/%3E, decoded back at
decoration time;
- a keyboard user focusing a skill pill while the skill list was still
loading got it stripped from the tab order permanently (no mouse, no
way back to the restore branch). The facade now exposes skillsLoaded
(record key presence), and the degrade strips tab semantics only once
the list HAS loaded — during loading the pill stays focusable and
self-heals when it arrives.
* fix(chat): re-pillify only mutated bubbles, strip native titles on pill links
* fix(composer): reject query-only mention hrefs, reset menu state on close, require a single skill pill for edit, handle Windows Markdown base dirs
* fix(composer): strip the leading slash in skill names, mark failed skill fetches loaded, keep parent segments escaping a Markdown base dir
* fix(composer): retry failed skill fetches while still marking them finished, resolve Markdown relatives per Windows segment
* fix(composer): reset the skills-loaded marker while a retry is in flight, keep the POSIX root as a Markdown base dir
* fix(composer): classify renderer-encoded Windows drives, keep forward-slash UNC roots, degrade dead skill pills on direct activation, let an armed goal preempt skill activation
* fix(composer): decode rendered labels percent-only, lift the highlight off stale rows on async skills, strip dead skill links' href, inline the probe spinner at the path tail
* refactor(composer): collapse mention dest encoding to one canonical percent rule, pinned by an exhaustive wire matrix
* fix(composer): keep non-ASCII mention paths literal in the canonical wire encoding
* refactor(composer): extract the composer rich-text cluster into @moonshot-ai/app-composer
* feat(composer): render user messages with the declarative ComposerText component, retiring the pillify pass
* docs(composer): move the wire-format spec into the app-composer package
* fix(composer): keep skill rows from stealing the mention menu's default highlight
* fix(composer): dedupe in-flight skill fetches per scope so only one request writes the loaded marker
* fix(composer): document the full label escape set in the wire spec, exempt only absolute filesystem paths in the preview resolver, reject empty-named skill links
* fix(composer): token-based mention tooltip line height, Windows trailing-separator folder classification, fragment-stripped mention action paths in ComposerText
* fix(composer): per-line mention link parsing, Windows folder separator fidelity, tooltip Escape focus return
- parseMentionLinks now parses line by line so a cross-line label never
pillifies on the message surface (textToDoc parity; spec section 5 and
invariant 5 updated)
- serializeMention no longer appends '/' to a folder path already ending
in a Windows separator
- mentionTooltip's Escape returns focus to the anchor pill before hiding,
instead of stranding it inside the inert bubble
- AGENTS.md: document the mention tooltip bubble as a component-primitive
exception alongside the dock overlay
* fix(composer): keep a degraded skill pill's href while the skill list is still loading
A Markdown skill pill's only focus hook is its href — stashing it during
the loading window dropped the anchor out of the Tab order for good, and
contentFor's restore branch only re-runs on hover/focus, so keyboard
users had no way back once the list arrived. Defer the href/tabIndex
strip until the list is loaded; the click stays swallowed by the
document-level routing either way.
* fix(composer): consume the tooltip dismissal Escape, copy bubble selections as wire text
- mentionTooltip only handles Escape while the bubble is open, and now
consumes it (preventDefault + stopImmediatePropagation): an unconsumed
Escape kept travelling to ConversationPane's document handler, so
closing a tooltip also aborted the running turn
- ComposerText wraps its inline flow in a copy interceptor: selections
fully inside the component are re-serialized from the pills' full
data-mention-* attrs, so copying a bubble yields the exact wire text
again instead of a truncated basename
* fix(composer): associate the tooltip with its pill, token the spinner stroke, key the probe spinner
- mentionTooltip gives the bubble a stable id and sets aria-describedby
on the anchor pill while shown (cleared on hide/retarget), so screen
readers announce the full path / skill description instead of only
the truncated label
- the probe spinner's ring stroke now rides --p-ring-stroke, the shared
ring-family token, instead of a hardcoded 1.5px
- the in-flight existence probe is tracked by its full key
(scope|actionPath): two pills can share a display path while probing
different action paths, and an earlier flight's settle must not clear
the spinner the current probe still needs
* fix(composer): token the mention icon stroke, split Windows tooltip paths, drop Markdown pill link navigation
- the pill glyph stroke rides the existing --p-hairline token instead of
a hardcoded 0.5px
- the path tooltip splits on both '/' and '\' and renders each
separator with its original character, so a Windows path no longer
bolds as one blob
- Markdown file pills (on hosts with openFile) and every skill pill
lose their href: middle-click and 'open in new tab' would otherwise
navigate a workspace path or kimi-code:// as a web URL, bypassing the
in-app routes. Explicit tabIndex + button role keep keyboard
semantics, and file pills gain an Enter/Space keydown that fires the
same action the click does
* fix(composer): degrade stale skill pills to plain references, generation-guard the activation failure restore
- desktop composer: a revived pill naming a skill that is gone from the
workspace no longer enters the auto-activation path once the skill
list is loaded — the daemon refusal plus restore looped the same
message forever, so it could never go out as a plain prompt. The new
skillsLoaded prop (client -> ConversationPane -> Composer) gates the
check so the loading window keeps the old attempt path
- App.vue (desktop + web): a monotonic send generation now guards the
failure restore on both activation branches. A user sending or
queueing the next message while the activation was in flight left the
composer empty again, and the old same-session + empty check then
re-filled the old command for an easy duplicate send
* fix(chat): keep the command-args styling for slash-typed skill activations
isCommandArgs now covers a skill activation only when its args do NOT
carry the skill as a pill: pill-composed activations keep the plain
bubble styling (the args ARE the message with the pill revived inline),
while slash-typed ones (/skill:name args) keep the skill-act-args
indent/muting and u-text-wrap-args spacing under the identity card,
same as a plugin command's
* fix(composer): move the probe spinner's baseline lift onto a token
The optical vertical-align now rides --p-mention-tip-spinner-lift in the
central token block (em-based, so it still tracks the tooltip's font
size) instead of a hardcoded coefficient in the shared sheet
* fix(composer): widen the mention tooltip's viewport margin to --space-3
The hover card sat a bare 8px (--space-2) from the window edge, which
reads as no gap at all for a floating surface. The margin is now a
single-sourced token (--p-mention-tip-vmargin, 12px) read by position()
via tokenPx and folded into the width cap, so the card keeps a visible
breathing room from the viewport on every side
51 lines
1.5 KiB
JSON
51 lines
1.5 KiB
JSON
{
|
|
"name": "kimi-code-web",
|
|
"version": "0.1.2",
|
|
"private": true,
|
|
"type": "module",
|
|
"scripts": {
|
|
"prepare:fonts": "node ../../scripts/prepare-fonts.mjs",
|
|
"predev": "npm run prepare:fonts",
|
|
"dev": "vite",
|
|
"predev:stub": "npm run prepare:fonts",
|
|
"dev:stub": "node dev/stub-daemon.mjs",
|
|
"prebuild": "npm run prepare:fonts",
|
|
"build": "vite build",
|
|
"typecheck": "vue-tsc --noEmit",
|
|
"test": "vitest run",
|
|
"check:style": "node scripts/check-style.mjs"
|
|
},
|
|
"dependencies": {
|
|
"@chenglou/pretext": "0.0.8",
|
|
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
|
"@moonshot-ai/app-client": "workspace:*",
|
|
"@moonshot-ai/app-composer": "workspace:*",
|
|
"@moonshot-ai/app-core": "workspace:*",
|
|
"@moonshot-ai/app-i18n": "workspace:*",
|
|
"@moonshot-ai/app-markdown": "workspace:*",
|
|
"@moonshot-ai/app-ui": "workspace:*",
|
|
"@rive-app/canvas": "2.38.5",
|
|
"@xterm/addon-fit": "^0.11.0",
|
|
"@xterm/xterm": "^6.0.0",
|
|
"katex": "^0.17.0",
|
|
"markstream-vue": "1.0.9-beta.1",
|
|
"mermaid": "^11.15.0",
|
|
"photoswipe": "^5.4.4",
|
|
"shiki": "^4.3.0",
|
|
"stream-markdown": "0.0.16",
|
|
"vue": "^3.5.35",
|
|
"vue-i18n": "^11.4.5"
|
|
},
|
|
"devDependencies": {
|
|
"@iconify-json/ri": "^1.2.10",
|
|
"@iconify-json/tabler": "^1.2.35",
|
|
"@moonshot-ai/vite-preset": "workspace:*",
|
|
"@vitejs/plugin-vue": "^5.2.4",
|
|
"typescript": "6.0.2",
|
|
"unplugin-icons": "^23.0.0",
|
|
"vite": "^6.3.3",
|
|
"vitest": "4.1.4",
|
|
"vue-tsc": "~3.2.0",
|
|
"ws": "^8.18.0"
|
|
}
|
|
}
|