Skip to content

Tags: harmoniqs/opencode

Tags

v1.18.10-amicode.23

Toggle v1.18.10-amicode.23's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Merge pull request #376 from harmoniqs/fix/digit-led-inline-math

fix(ui): render digit-led single-$ math — move currency guard to close side

v1.18.29-amicode.30

Toggle v1.18.29-amicode.30's commit message
Amicode binary v1.18.29-amicode.30

v1.18.29-amicode.29

Toggle v1.18.29-amicode.29's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Merge pull request #327 from harmoniqs/feat/harmoniqs-connect-dialog

feat(app): branded Harmoniqs AI connect entry point in the generic Connect Provider dialog

v1.18.29-amicode.28

Toggle v1.18.29-amicode.28's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Merge pull request #325 from harmoniqs/feat/harmoniqs-ai-provider

fix(session): strip tools for the harmoniqs no-tools custom provider

v1.18.29-amicode.27

Toggle v1.18.29-amicode.27's commit message
Amicode binary v1.18.29-amicode.27

v1.18.29-amicode.26

Toggle v1.18.29-amicode.26's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Preview as sidebar companion — reactive file viewer driven by Sidebar…

… and Chat (#931) (#315)

* feat(session-ui): extract editor-core.ts + consolidate preprocessMarkdown (#919)

Slice 1 of #912 — Preview Tab Redesign.

- Extract general-purpose CM6 setup functions (baseExtensions, editableExtensions,
  loadLanguage, buildThemeExtension, buildSyntaxHighlightStyle, detectMode,
  externalUpdate) from editable-diff-view-core.ts into editor-core.ts.
- Consolidate three divergent copies of preprocessMarkdown into markdown-utils.ts
  with the canonical version (newlines + trim).
- Export RENDERABLE_EXTENSIONS constant and isRenderable() helper from
  markdown-utils.ts for use by subsequent preview-tab slices.
- Add explicit package.json exports for the new .ts modules.
- editable-diff-view-core.ts re-exports from editor-core.ts for full
  backward compatibility — all 74 existing diff-view tests pass unchanged.
- session-preview-tab.tsx and session-review-file-preview-v2.tsx import
  preprocessMarkdown from the shared module.

* feat(app): Finder-style directory view + navigation state machine (#920)

Slice 2 of #912 — Preview Tab Redesign.

- Create preview-nav-state.ts with pure navigation state machine
  (navigateToFolder, selectFile, goBack) and filterDirectoryEntries().
- Create preview-directory-view.tsx — Finder-style folder/file listing
  with drill-down navigation.
- Create preview-file-view.tsx — file routing component (markdown only
  for now, stubs for image/PDF that Slice 5 fills).
- Rewrite session-preview-tab.tsx as thin orchestrator managing
  PreviewTabState with project-mode (useFile() tree.list/children) and
  no-project fallback (session-touched renderable files).
- 17 unit tests for navigation state transitions and directory filtering.

* feat(app): breadcrumb navigation + filetree dropdown + search (#921)

Slice 3 of #912 — Preview Tab Redesign.

- Create preview-search-utils.ts with parseBreadcrumbSegments() and
  searchRenderableFiles() (pure substring filter, regex-safe).
- Create preview-breadcrumb.tsx with clickable path segments, filetree
  dropdown (FileTreeV2 static mode via allowed prop), and folder-click
  interceptor on data-slot directory rows.
- Add search bar to session-preview-tab.tsx: replaces directory view
  with flat substring-matched results, Escape clears, integrated with
  navigation state (goBack clears search first).
- Add recursive tree walk + cache for dropdown/search file enumeration,
  refreshed when project becomes ready.
- 10 unit tests for breadcrumb parsing and file search.

* feat(session-ui): CodeMirror preview editor + mode rename raw→edit (#922)

Slice 4 of #912 — Preview Tab Redesign.

- Create preview-editor.tsx in session-ui: plain EditorView using
  editor-core.ts shared functions (baseExtensions, editableExtensions,
  loadLanguage, buildThemeExtension, detectMode).
- Full clipboard bridge: data-amc-clipboard="codemirror" attribute +
  __amcEditor stash with getSelectedText()/cutSelectedText() methods.
- Cmd/Ctrl+S via keymap extension triggers immediate save.
- External content updates via externalUpdate annotation.
- Rename mode "raw" → "edit" in PreviewFileState type, all components,
  and SegmentedControlV2 labels.
- Remove RawEditor textarea component (dead code).
- Update preview-file-view.tsx to use PreviewEditor for edit mode.

* feat(app): image/PDF rendering + file-type routing (#923)

Slice 5 of #912 — Preview Tab Redesign.

- Complete preview-file-view.tsx with image and PDF rendering branches:
  images via <img> centered with object-contain, PDFs via <embed>.
- Refactor file-type detection to getFileCategory() returning markdown/
  image/pdf/unsupported.
- Zoom controls (CSS transform: scale) applied consistently to all types.
- Mode toggle (Preview/Edit) hidden for non-markdown types (view-only).
- File URLs built via serverSDK /file/read endpoint for binary files.
- No-project fallback already scaffolded in Slice 2 using touchedFiles
  prop with isRenderable() filter.
- 12 unit tests for file-type routing and mode toggle visibility.

* feat(preview): show all non-ignored files in Finder listing (#925)

- filterDirectoryEntries() drops the isRenderable gate — all non-ignored
  files now appear in the Preview tab's directory browser
- walkTree collects all non-ignored file paths (not just renderable)
- searchRenderableFiles → searchFiles (broader scope, same logic)
- No-project fallback retains its isRenderable filter (unchanged)

Part of #925

* feat(preview): text files in CM6 editor + binary/error/oversize guards (#925)

- getFileCategory() returns 'text' instead of 'unsupported'; extensionless
  files (Makefile, Dockerfile, LICENSE) classify as 'text'
- fileType signal (null → text/binary/error/too-large) classifies content
  after async file.read() — checked before the category-based router
- Text files open directly in PreviewEditor (edit mode, no toggle)
- Binary → 'Binary file — cannot preview' placeholder
- Error → 'Could not read file' placeholder
- Oversize (>1 MB) → 'File too large to preview in this tab' placeholder
- Preview/Edit toggle shown only for markdown (unchanged)
- Switch/Match replaces nested Show for cleaner routing

Part of #925

* feat(preview): strip Finder, lift previewFile signal, empty state (#933)

Strip the Finder UI (directory listing, breadcrumbs, search, walk-tree cache)
from SessionPreviewTab. The Preview tab is now a companion viewer driven by
external file selection.

Changes:
- Gut preview-nav-state.ts to PreviewFileState type + createPreviewFileStates()
- Rewrite SessionPreviewTab: accepts previewFile Accessor<string|null>,
  shows empty state ('Select a file from the sidebar') when null
- Add previewFile to layout context ephemeral store (per-session, not persisted)
- SessionSidePanel reads previewFile from layout context, passes to Preview tab
- Delete preview-search-utils.ts (breadcrumb parsing + file search)
- Delete preview-breadcrumb.tsx and preview-directory-view.tsx
- Remove preview-search-utils from session-ui package exports
- Rewrite preview-nav-state tests: 7 tests for file-state persistence

Net: -967 lines (1121 deleted, 154 added). All 30 packages typecheck clean.
Closes harmoniqs/amicode#933

* feat(app): preview-file bridge listener — auto-opens Preview tab (#934)

Add a message listener in the session page for the 'preview-file' bridge
message from the amicode extension. On receipt:
1. Sets previewFile in the layout context (the ephemeral signal from #933)
2. Opens the Preview tab in the side panel (tabs().open + setActive)
3. Auto-opens the side panel if closed (reviewPanel.open)

The listener uses batch() to avoid intermediate renders.

Part of harmoniqs/amicode#934

* fix(file-handler): return binary response for absolute-path binary files (#934)

The content handler crashed with a 500 when reading binary files (images,
PDFs) via absolute paths outside the workspace. TextDecoder('utf-8',
{ fatal: true }) throws on invalid UTF-8 bytes — catch the error and
return { type: 'binary', content: base64, mimeType } instead.

Test (1 new): writes a PNG outside the workspace, requests it via
absolute path, and verifies the binary response shape.

* feat(preview): toolbar consolidation + binary file rendering (#934)

Toolbar:
- Save dot moves to Bar 1 (header), immediately after the filename
- Zoom controls move to Bar 2 (action bar), alongside the edit/preview toggle
- PreviewFileView exposes saveStatus via onSaveStatusChange callback
- Bar 2 always visible (zoom always accessible)

Binary files (images/PDFs):
- Images render via data URI (data:mime;base64,...) from the SDK response
- PDFs render via blob URL (base64 → Uint8Array → Blob → createObjectURL)
- Switch restructured: image/pdf category matches fire BEFORE the generic
  binary guard, so renderable binaries reach their renderer
- Removed dead fileUrl memo (was pointing at nonexistent /file/read endpoint)
- File loading effect detects renderable binaries (image/pdf category +
  base64 encoding) and stores them in binaryData signal instead of
  setting fileType='binary'

Tests (8 new/updated):
- classifyFileType: renderable image/PDF binary → 'text' pass-through
- Structural: zoom controls in PreviewFileView, not SessionPreviewTab
- Structural: save dot in SessionPreviewTab next to filename
- Structural: onSaveStatusChange prop on PreviewFileView
- Structural: zoomIn/zoomOut passed as props

* fix(csp): add object-src 'self' blob: for PDF embed in Preview tab (#934)

The server CSP did not declare object-src, so it fell back to
default-src 'self' — which silently blocked blob: URLs used by
<embed> for PDF rendering. Add the directive.

Test (1 updated): CSP integration test now asserts object-src.

* fix(preview): image zoom via CSS sizing, PDF via iframe (#934)

Images: Replace transform: scale() + origin-top-left with direct CSS
width sizing on the <img> element. The transform approach caused
scroll-jumps because the layout box stayed fixed at h-full while the
visual size changed — the overflow-auto parent had nothing to scroll.
Now zoom=150 sets width: 150% on the img, giving natural overflow.

PDFs: Replace <embed> with <iframe> for better webview compatibility.
The embed element is a plugin surface with inconsistent behavior across
webview contexts; iframe with a blob: URL is more reliable and the
browser's built-in PDF viewer handles it natively.

Tests (2 new structural):
- Image zoom block must not use transform: or origin-top-left
- PDF block must use iframe, not embed

* fix(preview): CSP frame-src for PDF iframe + defeat Tailwind max-width on image zoom (#934)

PDF blank white: the server CSP did not declare frame-src, so <iframe
src="blob:..."> fell back to default-src 'self' — which silently
blocks blob: URLs. Add frame-src 'self' blob:.

Image zoom capped at 100%: Tailwind preflight (base.css:222) sets
max-width: 100% on all <img>. The inline width: 150% was clamped.
Add max-w-none (overrides the preflight cap) and shrink-0 (prevents
flex-shrink from compressing the image back) to the <img> class list.

Tests:
- CSP integration test asserts frame-src 'self' blob:
- Structural test asserts max-w-none and shrink-0 on image element

* feat(preview): zoom floor at 100%, PDF zoom via transform (#934)

- Zoom floor raised from 50% to 100% — images and PDFs always fill at
  least the full panel width/height; zoom only goes in, never out past
  the natural fit.
- PDF iframe wrapped in a transform: scale() container with inverse
  width/height compensation so zoom works for PDFs too.

Tests (2 new): zoom floor assertion, PDF iframe has transform zoom.

* feat(preview): category-aware zoom floor — 100% for image/pdf, 50% for markdown/text (#934)

The zoom floor was global at 100%, preventing markdown and text files
from zooming out. Now the floor is determined by the file category:
image/pdf stay at 100% (never shrink below panel fit), while
markdown/text keep the original 50% floor.

The parent (SessionPreviewTab) keeps the global zoomOut floor at 50%.
PreviewFileView applies a higher floor via handleZoomOut() which
checks the category-specific zoomFloor() before delegating.

Test (1 updated): asserts parent has 50% floor and child has zoomFloor.

* fix(preview): replace PDF iframe with open-in-editor placeholder (#934)

Chromium's built-in PDF viewer is not available inside VS Code's
sandboxed webview iframes — <iframe src="blob:..."> silently renders
a white page regardless of CSP. No amount of CSP fixing resolves it.

Replace with a centered placeholder showing the filename, an icon,
and an 'Open in editor' button that posts an open-file message to
the extension host, which opens the PDF in a native VS Code tab
(via vscode.open — works with any installed PDF viewer extension).

Remove the dead pdfBlobUrl signal, blob creation effect, and cleanup.

Test (1 updated): PDF block must not contain iframe/embed, must
contain open-file action.

* feat(preview): render PDFs via PDF.js canvas, not iframe/placeholder (#934)

The browser's built-in PDF viewer is blocked in VS Code's sandboxed
webview iframes. Instead of a placeholder, use pdfjs-dist (Mozilla's
PDF renderer) to decode PDF binary data and paint each page onto
<canvas> elements — works in any context.

Architecture:
- New PdfCanvasView component (pdf-canvas-view.tsx): takes base64 PDF
  data, decodes → Uint8Array, uses getDocument() + page.render() to
  paint each page to a canvas. Pages stack vertically with gaps.
- Worker disabled (GlobalWorkerOptions.workerSrc = ''): main-thread
  parsing — fine for a side-panel preview. Avoids CSP and Vite worker
  bundling complexity.
- HiDPI: renders at devicePixelRatio × scale, CSS-sizes at 1× for
  crisp text on Retina.
- Zoom: maps the existing zoom() prop (percentage) to PDF.js scale
  factor. Re-renders canvases on zoom change.
- 'Open in editor' kept as secondary action below the rendered pages.

Tests (34 pass):
- PDF block must not contain iframe/embed/placeholder
- PDF block must use PdfCanvasView
- pdfjs-dist imported and worker configured
- Open-in-editor action present in canvas view

* fix(preview): use globalThis.pdfjsWorker for main-thread PDF parsing (#934)

Setting workerSrc='' causes a double-fault in pdfjs-dist v6: the
getter throws 'No workerSrc specified', the catch falls through to
#setupFakeWorker, which calls the same getter via dynamic import()
and throws again — 'Setting up fake worker failed'.

Fix: import the worker module directly and inject it on
globalThis.pdfjsWorker. This is the one code path in pdfjs-dist v6
that bypasses both new Worker() and the workerSrc getter entirely.
PDFWorker.#initialize() sees the handler, routes through LoopbackPort,
and runs everything on the main thread. No CSP issue, no blob worker,
no external fetch.

* fix(preview): safe centering for full scroll reach on images and PDFs (#934)

When a flex container centers a child that overflows, the negative
(left/top) overflow is clipped — CSS scroll only covers positive
offsets. Zoomed images and PDFs were unreachable on their left edge.

Fix: switch image and PDF wrappers to inline-flex + min-w-full +
min-h-full. The wrapper's width = max(content, container), so
centering happens inside a box that covers the full scroll area.
When the content fits, it's centered; when it overflows, the wrapper
grows to match and scroll covers the full content.

* feat(preview): pinch-to-zoom and shift+scroll zoom (#934)

Trackpad pinch fires as WheelEvent with ctrlKey: true in Chromium;
shift+scroll is the mouse-wheel equivalent. Both now trigger smooth
continuous zoom in the Preview tab's content area.

Implementation:
- SessionPreviewTab exposes onZoomChange(value) — arbitrary zoom
  clamped to [50, 200], passed alongside the existing zoomIn/zoomOut
  step functions (buttons still use those).
- PreviewFileView attaches a wheel listener on the scroll container
  via addEventListener with { passive: false } (Solid's onWheel is
  passive by default and can't preventDefault).
- Exponential scaling (Math.exp(-delta * 0.003)) — smooth, invertible,
  handles both tiny trackpad pinch deltas and large scroll-wheel jumps.
- Category-aware floor applied (100% for image/pdf, 50% for text/md).
- Normal scroll (no modifier key) passes through unaffected.

Tests: 37 structural tests pass.

* feat(preview): PDF pages fit container width at 100% zoom (#934)

PDF pages previously rendered at their intrinsic size (612×792 CSS px
for Letter) regardless of panel width. Images already adapted because
they use CSS width: zoom% (relative to container).

Fix: compute a base scale that fits the PDF page width to the
container at 100% zoom (containerWidth / intrinsicWidth), then
multiply by zoom/100 for user zoom on top. A ResizeObserver tracks
container width so pages reflow when the panel is resized.

At 100%: page fills the panel width exactly (like images).
At 200%: twice the panel width — scrollable.
Panel resize: pages reflow to fit the new width.

Each page computes its own base scale from its intrinsic width, so
mixed-size documents (Letter + A3) each fit the container individually.

Tests: 38 structural tests pass.

* fix(preview): observe scroll container not wrapper, breaking feedback loop (#934)

The ResizeObserver was watching the wrapper div (inline-flex), whose
size depends on the page canvases, whose size depends on containerWidth,
which is set by the observer — a feedback loop causing blank rendering
and severe lag from rapid re-render oscillation.

Fix: observe the PARENT element (the scroll container) instead. Its
width is flex-determined and independent of page content. It only
changes when the user resizes the panel, which is exactly what we
want. No feedback loop possible.

Also:
- Fix double-padding subtraction: contentBoxSize already excludes
  padding, so subtracting WRAPPER_PADDING from it was wrong when the
  observed element had padding. The scroll container has no padding,
  so subtracting WRAPPER_PADDING (the wrapper's p-4) once is correct
  and consistent between initial measurement and observer callback.
- Add explicit RenderTask.cancel() in effect cleanup: cancels any
  in-flight page.render() when zoom/containerWidth changes or the
  component unmounts, preventing stale renders from painting over
  the canvas.

Race condition audit — all paths verified safe:
- containerWidth=0 guard prevents render before measurement
- cancelled flag + onCleanup handles stale getPage() callbacks
- activeRender.cancel() handles in-flight render tasks
- ResizeObserver cleanup on unmount prevents orphan callbacks

* feat(preview): zoom anchors to viewport center, not top-left (#934)

Before: zooming in/out shifted the view toward the top-left corner
because scrollLeft/scrollTop stayed constant while content scaled.

After: adjustScrollForZoom computes the viewport center in content
coordinates before the zoom, then repositions scroll after the DOM
updates so the same content point remains centered.

Applied to all three zoom paths:
- Pinch/Ctrl+scroll (wheel handler)
- Shift+scroll (mouse wheel)
- +/- toolbar buttons

Uses requestAnimationFrame to let the DOM update first: image CSS
reflows synchronously, PDF canvas dimensions settle as a microtask
(getPage().then()), and rAF fires after both — so scrollWidth and
scrollHeight reflect the new content size before we set scroll pos.

Tests: 39 structural tests pass.

* feat(preview): raise zoom ceiling from 200% to 500% (#934)

Four references updated: zoomIn step clamp, onZoomChange clamp,
wheel handler clamp, and the input validation range. Floor unchanged
(50% for text/markdown, 100% for image/pdf).

* fix(preview): use absolute pixel width for images, fixing left-scroll clipping (#934)

Root cause: `width: ${zoom}%` inside an inline-flex wrapper creates a
circular CSS dependency. The wrapper sizes from the image's intrinsic
(natural) dimensions, then the percentage resolves to a larger value,
overflowing the wrapper. justify-center pushes the left overflow into
unreachable negative scroll territory.

Fix: measure container width via ResizeObserver (same pattern as the
working PdfCanvasView) and compute image width as absolute pixels:
`(containerWidth - padding) * zoom / 100`. The inline-flex wrapper
now sizes correctly around the deterministic pixel value.

* feat(preview): zoom layout redesign, editor zoom, left-align toolbar, dirty dot (#937)

Four changes to the Preview tab toolbar and indicators:

1. Editor zoom (CM6 font-size): PreviewEditor gains a zoom prop backed by
   a CM6 Compartment that reactively reconfigures fontSize. Fixes code/text
   files and markdown-edit-mode not responding to zoom (all three share the
   same PreviewEditor component).

2. Zoom layout redesign: single pill with [editable %] [reset ↻] [+ over −].
   The percentage input is now editable (removed readOnly), the reset button
   jumps to 100%, and +/− are vertically stacked.

3. Left-aligned toolbar: removed the flex-1 spacer that pushed controls right.
   Mode toggle and zoom now sit flush-left.

4. VS Code-style dirty dot: replaced the transient saving/saved indicator with
   a simple gray dot that appears when unsavedContent != null and disappears
   when the file is clean. Matches VS Code and Files Changed behavior.

* fix(preview): zoom input commits on Enter/blur only, clamps to valid range

The zoom percentage input was firing onZoomChange on every keystroke via
onInput, causing the viewport to jump while typing (e.g. typing '200'
triggered zoom to 2 → 20 → 200).

- Removed onInput handler entirely — typing is free, no zoom updates
- Commit happens on Enter (triggers blur) or click-away (blur)
- Escape reverts to the current zoom and blurs without committing
- Values are clamped to [zoomFloor(), 500] on commit — typing 9999
  gives 500, typing 10 on an image (floor 100) gives 100

* fix(preview): zoom pill before edit/preview toggle in toolbar

Swaps the toolbar order so the zoom controls come first (left), followed
by the edit/preview mode toggle. The zoom pill is always visible; the
toggle only appears for markdown files — putting the stable control first
avoids layout shift.

* fix(preview): use SDK client for file saves — fixes silent 401 on auth-protected servers

The saveFile function used a raw fetch() to POST /file/write with no
Authorization header. The Amicode server requires auth (OPENCODE_SERVER_PASSWORD),
so the request got a 401 — but fetch() doesn't reject on HTTP errors, so
.then() fired, setSaveStatus('saved') ran, and the user saw no indication
that the save silently failed.

Fix: replace both raw fetch calls (saveFile + onCleanup flush) with
serverSDK().client.file.write(), which goes through createSdkForServer and
injects the Authorization: Basic header. This matches the review panel's
pattern (review-panel-v2.tsx:183).

Also:
- saveFile now uses async/await + try/catch (SDK throws on non-2xx)
- Clears unsavedContent to null after successful save (dirty dot disappears)
- Widened onUnsavedContent prop to accept string | null

* fix(preview): visible unsaved dot + preserve trailing newlines on file read

Two fixes:

1. Unsaved-changes dot was invisible — bg-text-faint is not a valid
   Tailwind v4 utility in this app (the v2 design system uses v2- prefix).
   Fixed to bg-v2-text-text-faint. Also fixed the empty-state icon
   (text-text-faint → text-v2-text-text-faint).

2. The /file/content handler called .trim() on decoded text (lines 112
   and 149 of handlers/file.ts), stripping trailing newlines. When a
   POSIX file ending with \n was read → edited → saved (even reverted to
   'original'), the trailing newline was lost, creating a phantom git
   diff. Removed both .trim() calls — the content should round-trip
   faithfully.

   The Files Changed tab is NOT affected — it reads content from the
   snapshot diff pipeline (git objects), not from /file/content.

* feat(preview): remove autosave — save only on explicit Cmd+S

The Preview tab was autosaving on a 1-second debounce after every
keystroke. This caused unexpected writes to disk (and phantom git diffs
when the user was just browsing/experimenting).

Removed:
- debouncedSave function and saveTimer variable
- The debouncedSave() call from handleEdit (now only tracks dirty state)
- The onCleanup flush (existed to flush the debounce timer)

Kept:
- handleImmediateSave (Cmd+S) — the only save path
- saveFile with SDK auth + try/catch + unsavedContent clearing
- The unsaved dot indicator (appears on first edit, clears on Cmd+S)

* feat: zoom pill grayed in edit mode (Preview), floating overlay (Files Changed)

Preview tab:
- Added isEditing() signal — returns true for text/code (always editing),
  markdown-in-edit-mode, and false for images/PDFs/markdown-in-preview.
- Zoom pill gets opacity-40 + pointer-events-none when isEditing() is true.
  The pill stays visible (signals 'zoom exists') but is non-interactive.
- Removed zoom prop from both PreviewEditor call sites — the editor always
  renders at base font size, avoiding confusing zoom mismatch when switching
  between preview and edit modes.

Files Changed tab:
- Added local zoom state (createSignal(100)) with zoomIn/zoomOut/onZoomChange.
- Added a floating zoom overlay (absolute positioned, z-index 20) inside the
  markdown preview branch — only visible when isPreviewMd() is true, disappears
  entirely in unified/split diff and edit modes.
- Markdown preview now applies CSS transform: scale() with the zoom value,
  matching the Preview tab's pattern.
- Pill styling: semi-transparent backdrop with blur, matching the lock
  indicator's floating pattern.

* feat(files-changed): auto-hide zoom pill on idle (2s)

The floating zoom overlay in the Files Changed markdown preview now
auto-hides after 2 seconds of mouse inactivity:

- Pill starts visible on first render (initial 2s reveal)
- Mouse movement over the preview area shows the pill and resets the timer
- Mouse leaving the preview area hides the pill immediately
- Hovering over the pill itself pauses the timer (no fade mid-interaction)
- Leaving the pill resumes the idle timer
- Opacity transition (200ms ease) for smooth fade in/out
- pointer-events: none when hidden so it doesn't block content interaction

* feat(files-changed): pinch/wheel zoom in markdown preview

Trackpad pinch (ctrlKey + wheel) and shift+scroll now trigger zoom in
the Files Changed markdown preview. The handler:

- Checks ctrlKey/shiftKey — normal scroll passes through untouched
- preventDefault() to suppress browser default pinch-to-zoom
- Exponential zoom factor (Math.exp(-delta * 0.003)) for smooth response
- Clamped to [50, 500] via onZoomChange
- Shows the zoom pill and resets the 2s idle timer during the gesture

Attached via addEventListener with passive: false on the preview wrapper
ref (Solid's onWheel is passive by default and can't preventDefault).

Normal scroll intentionally does NOT show the pill — keeps the reading
experience clean.

* feat(preview): floating overlay controls, no action bar

Replaces the fixed action bar with floating overlay controls anchored
to the top-right of the content area, matching the Files Changed tab's
pattern. Both tabs now have a consistent floating zoom pill design.

Layout:
- Single absolute-positioned flex row (top: 8px, right: 8px, z-index: 20)
- Zoom pill on the left, edit/preview toggle on the right
- Semi-transparent backdrop blur on both controls
- No action bar — maximizes content area

Behavior:
- Both controls auto-hide after 2s of mouse inactivity
- Mouse movement over the content area reveals them
- Hovering over the controls pauses the idle timer
- Pinch/wheel zoom reveals the controls and resets the timer
- Zoom pill disappears entirely in edit mode (<Show when={!isEditing()}>)
- Mode toggle only renders for markdown files
- 200ms opacity transition for smooth fade

For text/code files (always in edit mode), no controls render at all.
For images/PDFs, only the zoom pill appears (no mode toggle).

* fix: nudge floating overlays left to clear scrollbar (right: 14px)

* fix: update httpapi exercise to expect untrimmed file content

* fix: add explicit preview-editor export to session-ui package.json

v1.18.29-amicode.25

Toggle v1.18.29-amicode.25's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(app): repair OpenAI browser OAuth (#316)

* fix(app): repair OpenAI browser OAuth

* fix(app): preserve external link handlers

v1.18.29-amicode.24

Toggle v1.18.29-amicode.24's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Merge pull request #313 from harmoniqs/candidate-dispatch-race

fix(release): make dispatch own fork tags

v1.18.29-amicode.23

Toggle v1.18.29-amicode.23's commit message
Merge branch '878-shift-tab-agent-cycle' into local/amicode (#311)

v1.18.10-amicode.22

Toggle v1.18.10-amicode.22's commit message
feat(chat): yellow answers, no YOU label, and a WAAPI merge on the bu…

…bble lock

- the answered-question's answer renders as the prompt bubble's yellow
  chip, right-aligned to the user's side (same --prompt-bubble-* tokens)
- the sticky prompt bubble drops the YOU label — the chip speaks for itself
- hand-offs (scroll lock-over, click, send) merge via a 240ms WAAPI pass;
  the chip element persists — a keyed remount + CSS entrance strobed under
  smooth scrolling, tried and reverted same day