Skip to content

dagql outer shell#416

Open
vito wants to merge 221 commits into
mainfrom
workspace-plumbing-dagql-dagql
Open

dagql outer shell#416
vito wants to merge 221 commits into
mainfrom
workspace-plumbing-dagql-dagql

Conversation

@vito
Copy link
Copy Markdown
Owner

@vito vito commented Mar 18, 2026

(comparing ci to workspace-plumbing)

kpenfound and others added 12 commits March 10, 2026 15:56
…ger#11949)

* fixes needed for running integration tests with a generated client

Signed-off-by: kpenfound <kyle@dagger.io>

* fix go:lint

Signed-off-by: kpenfound <kyle@dagger.io>

* cleanup unneeded changes, thanks Erik

Signed-off-by: kpenfound <kyle@dagger.io>

* cleanup unneeded changes

Signed-off-by: kpenfound <kyle@dagger.io>

* fix for nested exec errors

Signed-off-by: kpenfound <kyle@dagger.io>

---------

Signed-off-by: kpenfound <kyle@dagger.io>
Signed-off-by: kpenfound <kyle@dagger.io>
…ees (dagger#11990)

Running `git config -l -z` from a directory with a broken .git pointer
(e.g. a git worktree mounted into a container) causes a fatal exit 128.
Fix by running from a temp directory so only system/global config is read,
and treat CONFIG_RETRIEVAL_FAILED as non-fatal since git config forwarding
is best-effort (only used for url.*.insteadOf mappings).

Signed-off-by: Yves Brissaud <yves@dagger.io>
* Fix CLI exiting 0 even when a failure occurred

In some error paths, the CLI can already have a concrete error while the resolved exit code is still the default zero value. When that happens, exiting 0 is misleading and can cause CI to treat a failed call or run as successful.

Harden the CLI exit handling so that if an error is present, an exit code of 0 is normalized to 1 instead of being treated as success. This keeps the current behavior for real nonzero exits while avoiding false-success outcomes when the exit code payload is missing or left at its zero value.

Signed-off-by: Erik Sipsma <erik@sipsma.dev>

* Avoid exiting 0 when the primary span failed

Add another hardening layer so the CLI does not report success when the primary command span itself ended in a failed state.

If the frontend is otherwise about to return nil, but the primary span is marked failed, we now convert that into a nonzero exit. This covers cases where the failure is clearly reflected in top-level span output, but the explicit error signal has been lost somewhere along the way.

The intent is straightforward: there should not be a situation where the primary span is failed while the CLI still exits 0, because that can make CI treat a failed call or run as successful.

Signed-off-by: Erik Sipsma <erik@sipsma.dev>

* Move exit code normalization onto ExitError

Follow up on review feedback by moving the zero-code hardening onto idtui.ExitError itself.

ExitError already carries both the raw exit code and the originating error, so this keeps the normalization logic with the type instead of spreading it across CLI call sites. That keeps the behavior the same while making the callers simpler and less error-prone.

Signed-off-by: Erik Sipsma <erik@sipsma.dev>

---------

Signed-off-by: Erik Sipsma <erik@sipsma.dev>
Signed-off-by: Tibor Vass <teabee89@gmail.com>
…ger#12004)

Bumps the sdk-java group with 4 updates in the /sdk/java directory: [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom), [com.palantir.javapoet:javapoet](https://github.com/palantir/javapoet), [org.mockito:mockito-core](https://github.com/mockito/mockito) and [io.opentelemetry:opentelemetry-bom](https://github.com/open-telemetry/opentelemetry-java).


Updates `com.fasterxml.jackson:jackson-bom` from 2.18.6 to 2.21.1
- [Commits](FasterXML/jackson-bom@jackson-bom-2.18.6...jackson-bom-2.21.1)

Updates `com.palantir.javapoet:javapoet` from 0.11.0 to 0.12.0
- [Release notes](https://github.com/palantir/javapoet/releases)
- [Commits](palantir/javapoet@0.11.0...0.12.0)

Updates `org.mockito:mockito-core` from 5.22.0 to 5.23.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](mockito/mockito@v5.22.0...v5.23.0)

Updates `io.opentelemetry:opentelemetry-bom` from 1.59.0 to 1.60.1
- [Release notes](https://github.com/open-telemetry/opentelemetry-java/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-java/blob/main/CHANGELOG.md)
- [Commits](open-telemetry/opentelemetry-java@v1.59.0...v1.60.1)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.21.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: sdk-java
- dependency-name: com.palantir.javapoet:javapoet
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: sdk-java
- dependency-name: org.mockito:mockito-core
  dependency-version: 5.23.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: sdk-java
- dependency-name: io.opentelemetry:opentelemetry-bom
  dependency-version: 1.60.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: sdk-java
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the docs group in /docs with 3 updates: [posthog-js](https://github.com/PostHog/posthog-js), [sass](https://github.com/sass/dart-sass) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `posthog-js` from 1.359.1 to 1.360.2
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/compare/posthog-js@1.359.1...posthog-js@1.360.2)

Updates `sass` from 1.97.3 to 1.98.0
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](sass/dart-sass@1.97.3...1.98.0)

Updates `@types/node` from 25.3.5 to 25.5.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: posthog-js
  dependency-version: 1.360.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docs
- dependency-name: sass
  dependency-version: 1.98.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docs
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: docs
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* refactor(core): introduce ModuleRuntime interface

Abstract module execution behind a ModuleRuntime interface so that
runtimes aren't required to use a Container. ContainerRuntime wraps
the existing container-based execution path. The Runtime interface
now returns ModuleRuntime instead of a container directly, and
ModuleFunction.Call delegates to the runtime's Call method.

* feat(sdk): add native Dang runtime

Add the Dang SDK as a builtin SDK with a native runtime that executes
directly in the engine process instead of in a container. The DangRuntime
implements ModuleRuntime, sets up an HTTP listener for nested GraphQL
clients, loads .dang files via dang.RunDir, and handles module init,
constructor calls, and function dispatch natively.

Includes: dang_sdk.go, SDK loader/consts registration, engine build
integration, stub entrypoint binary, and a telemetry fix to prevent
log pollution from native runtimes.

* feat(sdk): add persistent caching for native Dang runtime

The Dang runtime evaluates module functions in-process without
containers, bypassing buildkit's LLB cache that container-based
runtimes get for free via withExec. This meant every session
re-evaluated Dang functions even when inputs hadn't changed.

Introduce DangEvalOp, a Dang-specific buildkit custom op whose
Exec method reconstructs the module infrastructure from the dagql
server and runs the Dang interpreter. The op is fully JSON-
serializable: it stores the module source ID, schema file ID,
execution metadata, and function call data. On cache hit, buildkit
returns the cached snapshot directly without calling Exec.

* refactor(sdk): unify all Dang eval through DangEvalOp

Move module initialization through the same DangEvalOp path as
regular function calls, eliminating the special-case evalInit
method. Move initModule and all typedef helpers (createFunction,
createObjectTypeDef, dangTypeToTypeDef, etc.) from dang_sdk.go
into dang_op.go. Now dang_sdk.go is pure SDK interface glue and
dang_op.go contains all Dang evaluation logic.

Signed-off-by: Alex Suraci <alex@dagger.io>
* fix: Handle non-SHA referenced module dependencies

Fixes: dagger#11996
Signed-off-by: Mike Glazer <mike.glazer@gmail.com>

* test: replace the synthetic named-pin regression with a real remote fixture

The bug is in remote module dependency resolution. The previous regression test covered that path through a synthetic local Git service plus transport-specific setup, which made the failure mode noisier than it needed to be.

Replace it with a regression test that uses github.com/dagger/dagger-test-modules/context-git, which we already maintain as part of our end-to-end Git/module coverage.

This does introduce a network call, but that is the better tradeoff here. The bug only matters on the remote module path, and this fixture already matches the environment we rely on for real Git dependency tests.

The new test covers both branch-name and tag-name pins, which is the actual behavior we need to preserve: a non-SHA pin is a named ref, not a commit.

Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>

* core: resolve named dependency pins as refs instead of commits

A dependency pin can be either:
- a commit SHA
- a named ref, such as a branch or a tag

The bug was that modulerefs did not keep that distinction. For unversioned dependency sources, it resolved the repository through head() and still forwarded pinCommitRef through the commit override path even when the pin was a branch or tag name.

That was already the wrong contract. A commit override is for an object id. A named pin should travel through ref(name: ...).

The --no-tags change did not create this bug; it exposed it. Before that change, extra fetched tag state could sometimes hide the invalid commit-vs-ref conflation. Once that masking disappeared, the bug surfaced as checkout failures like "sha fetch unsupported by remote".

Fix the problem at the source:
- only treat the pin as commit when it is a real SHA
- resolve non-SHA pins as named refs
- keep the head() path only for the truly unversioned case

That restores a clean contract:
- SHA pin => exact commit
- named pin => exact named ref
- no pin and no explicit ref => repository HEAD

Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>

* git: enforce the commit override contract

The previous compatibility change made lower layers tolerant of invalid commit overrides by silently ignoring non-SHA values.

I do not want that contract to be soft.

Once modulerefs constructs the right state, lower layers should enforce their boundary instead of guessing what the caller meant:
- ref(name: ...) resolves named refs
- commit overrides are commit SHAs

Make that explicit in the Git schema and in HEAD resolution. Invalid commit overrides now fail immediately instead of being dropped on the floor.

This is intentionally stricter than the compatibility layer it replaces. I prefer an explicit contract to intertwined fallback behavior: the caller decides whether a pin is a ref or a SHA, and the lower layers reject invalid state.

Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>

* test: drop branch pin case, keep only tag

The branch-name test case ("context-git") relies on a branch in
dagger-test-modules that could be pruned at any time. Tags are immutable
and exercise the same ref(name: ...) code path, so keep only the v1.2.3
tag case.

Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>

---------

Signed-off-by: Mike Glazer <mike.glazer@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Co-authored-by: Guillaume de Rouville <guillaume@dagger.io>
…#10510)

* feat(secretprovider): add Google Cloud Secret Manager support

This adds support for using Google Cloud Secret Manager as a secret provider
in Dagger, enabling secure secret retrieval from GCP environments.

This was tested with the following command:

```shell
DAGGER_TEST_GCP_SECRETS=1
GCP_PROJECT_ID=rawkode-academy-production
GCP_TEST_SECRET_NAME=KNOWN_SECRET
GCP_TEST_SECRET_VALUE=KNOWN_VALUE
go test -v -timeout 30s -run "^TestSecretGCP$" github.com/dagger/dagger/core/integration
```

Key features:
- Support for multiple authentication methods:
    - Service account key files (via GOOGLE_APPLICATION_CREDENTIALS)
    - Application Default Credentials (ADC)
    - Workload Identity (on GKE)
    - GCE metadata service (on Compute Engine)
- Secret reference formats:
    - Short form: gcp://secret-name (requires GCP_PROJECT_ID env var)
    - Full form: gcp://projects/PROJECT/secrets/SECRET/versions/VERSION
    - Latest version: gcp://secret-name/versions/latest
- Built-in caching with configurable TTL via query parameter (?ttl=5m)
- Comprehensive error handling and validation

Implementation details:
- Uses official Google Cloud Go SDK (cloud.google.com/go/secretmanager)
- Thread-safe with mutex protection for concurrent access
- Efficient client reuse across multiple secret retrievals
- Supports all standard GCP authentication flows

Testing:
- Unit tests for URL parsing and validation
- Integration tests covering authentication and secret retrieval
- Documentation with setup instructions and examples

Signed-off-by: David Flanagan <david@rawkode.academy>

* go fmt and remove deleted docs page

Signed-off-by: kpenfound <kyle@dagger.io>

---------

Signed-off-by: David Flanagan <david@rawkode.academy>
Signed-off-by: kpenfound <kyle@dagger.io>
Co-authored-by: kpenfound <kyle@dagger.io>
* initial tuist cutover

Signed-off-by: Alex Suraci <alex@dagger.io>

* idtui: migrate frontend_pretty to idiomatic tuist components

Restructure frontend_pretty.go from a monolithic component with manual
dirty tracking into idiomatic tuist components where dirty propagation
flows naturally through the tree. Net reduction of ~150 lines.

Phase 1: Eliminate fe.mu
- All exporter goroutines (spans, logs, metrics) now use tui.Dispatch()
  to mutate state on the UI goroutine instead of locking fe.mu
- Copy OTel slices before dispatching (SDK reuses them after return)
- Use context.Background() in dispatched callbacks (caller ctx may expire)
- Metric exporter uses synchronous dispatch (ResourceMetrics is reused)
- All public methods (SetClient, SetVerbosity, SetPrimary, SetCloudURL,
  SetSidebarContent, Shell, RevealAllSpans, Close) dispatch to UI goroutine
- Remove fe.mu, flush(), markExporterDirty(), exporterDirty map

Phase 2: Extract SpinnerView component
- Self-ticking component (33ms via OnMount goroutine, stopped on dismount)
- Only mounted when span is running; completed spans have zero cost
- SpanRowView.syncSpinner manages creation/removal based on span state
- RenderChild in SpanRowView.Render handles mount/dismount lifecycle
- Remove global frameLoop goroutine, fe.fps, fe.now fields
- statusIcon() uses per-span spinner with time.Now() fallback for effects

Phase 3: Focusable for focus tracking
- SpanRowView implements tuist.Focusable (SetFocused callback)
- focus() calls tui.SetFocus(spanRow) so old/new rows re-render

Phase 4: Eliminate viewport windowing
- renderProgress() iterates ALL rows instead of windowing around focus
- Remove renderLines() (100-line viewport algorithm)
- Tuist's diff renderer skips unchanged offscreen lines automatically
- Merge renderContent() into Render() directly

Phase 5: Clean up string-builder rendering
- Remove fe.view/fe.viewOut string builders; build directly in Render()
- Remove spanRowKey struct and spanRowKeyFor() (manual dirty tracking)
- Replace frameLoop with scheduleKeypressClear() one-shot timer

Requires tuist v0.0.0-20260303194803-b28414493014 which mounts all
RenderChild children (not just MouseEnabled ones).

Signed-off-by: Alex Suraci <alex@dagger.io>

* idtui: fix focus visibility and line wrapping artifacts

Two fixes for the interactive TUI:

1. Truncate lines to terminal width: Lines produced by renderCall
   (especially string literals like execMD JSON) can far exceed the
   terminal width. The terminal wraps them to multiple physical rows,
   but tuist's diff renderer counts logical lines for cursor math.
   The mismatch causes the cursor to land mid-line, corrupting the
   display with repeated/garbled content. Fix: truncate every line
   to ctx.Width using muesli/reflow/truncate (ANSI-aware) before
   returning from Render().

2. Cap progress output after focus: When focus moves up, rows below
   push the focused row offscreen, and re-rendering the keymap scrolls
   the terminal back down, hiding the focus. Fix: renderProgress now
   limits 'after' rows to ~50% of remaining screen budget. Rows above
   focus scroll into terminal scrollback naturally (preserving history).
   When everything fits on screen, nothing is removed.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: refactor to tree-based rendering with SpanTreeView

Replace row-based rendering (SpanRowView) with tree-based rendering
(SpanTreeView) to fix stale parent ancestor prefixes (colorful line
guides).

Key changes:

- SpanTreeView renders a TraceTree node and its children recursively,
  replacing the flat SpanRowView that rendered one row at a time.

- Prefixes (tree-line art like ├╴, │, ╰╴) are computed top-down by
  parent SpanTreeViews, avoiding the stale-prefix problem where each
  row independently walked up the TraceRow.Parent chain and could
  cache outdated parent colors.

- All component state mutations (prefix, children, focus, spinners)
  happen in syncSpanTreeState(), called from recalculateViewLocked()
  during event handlers and Dispatch callbacks. Render() is a pure
  function that only reads state and produces output.

- Added indentFunc override on renderer so SpanTreeView can inject
  pre-computed prefixes without rewriting all rendering functions.

- FinalRender path unchanged: still uses flat renderRow with original
  fancyIndent (no component caching needed post-TUI).

- TraceRow kept for focus/navigation (flat index, previous/next
  pointers); rendering uses TraceTree hierarchy.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: fix gap lines missing parent bar for last children

Gap lines between sibling children were using child.prefix.cont, which
for the last child omits the parent's bar column (showing spaces instead
of │). The gap is *above* the last child though, so the parent bar should
still be visible.

Fix: compute a childrenGapPrefix on each SpanTreeView during state sync
(parent's forChildren + parent's own colored │ column) and use that for
all gap lines between children.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: fix indentation, scrolling, and debug logging

- Fix indentFunc to fall through to original fancyIndent for synthetic
  rows (e.g. renderErrorCause), preventing double indentation. The
  indentFunc now returns bool to signal whether it handled the row.

- Remove viewport windowing from renderProgressTree. Instead, render all
  lines into scrollback and truncate below the focused item so it stays
  onscreen. Focus line is found by walking the tree's render metadata.

- Move all SpanTreeView state sync (prefix, focus, children, spinners)
  into syncSpanTreeState() called from recalculateViewLocked(), making
  Render() stateless. Remove Invalidate() from tuist.

- Add TUIST_LOG env var to enable tuist render debug logging.

- Update tuist to v0.0.0-20260304010918-df8947963e5e for component
  stats propagation through RenderChild.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: remove dual focus tracking, add TODO.md

Remove Focusable interface from SpanTreeView. Focus state is now
solely managed by syncSpanTreeState (syncing st.focused from
fe.FocusedSpan). tui.SetFocus is kept for keyboard routing only.

Add TODO.md documenting remaining architectural improvements. Items
2 (scroll truncation) and 3 (indentFunc fallthrough) were reviewed
and found to be working correctly with tuist's design — marked as
KEEP.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: restructure Render() to line-based composition

Replace the string-builder-then-split pattern in frontendPretty.Render()
with direct []string line assembly:

- renderProgressTree → renderProgressLines (returns []string)
- Extract renderLogsLines, renderEditlineLines, renderFormLines,
  renderKeymapLines as line-returning helpers
- Render() assembles output via append, no string builder
- Remove dead finalRender branch from renderProgressLines
- Sidebar compositing still uses string join/split (lipgloss requires it)

This is phase 1 of TODO item 4. Phase 2 would extract these into proper
tuist components with their own Compo for render caching.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: replace sidebar with notification bubble overlays

Replace the old sidebar system (string-based, steals content width via
lipgloss JoinHorizontal) with notification bubbles rendered as tuist
overlays.

Each SidebarSection becomes a NotificationBubble component displayed
as an overlay anchored to the bottom-right of the content. Multiple
sections stack upward. Bubbles have rounded-corner borders with the
title embedded in the top border:

  ╭─ Title ─── keymap ──╮
  │ content here         │
  ╰──────────────────────╯

Benefits:
- Content gets full terminal width (no sidebar width stealing)
- Each bubble is a tuist Component with its own render cache
- Overlays are composited by tuist's overlay system, not lipgloss
- Removes renderSidebar, viewSidebar, renderWithSidebar, haircut
- Removes sidebarWidth, sidebarBuf fields

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: update TODO.md — mark item 4 as done

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: decouple ShellHandler from tea.Cmd pattern

Replace tea.Cmd returns in ShellHandler interface with explicit
async work functions:

- ReactToInput: returns func() (async work) or nil (not handled),
  instead of tea.Cmd. Caller runs async work in a goroutine.
- Prompt: returns (string, func()) instead of (string, tea.Cmd).
  The func() is optional async init work.

The frontend runs async work via runShellAsync(), which spawns a
goroutine and dispatches updatePrompt + re-render when done.

This fixes a race condition where execTeaCmd ran shell operations
(h.runner.Run, h.llmSession.SyncToLocal, etc.) in background
goroutines that accessed shared state concurrently. Now the shell
handler returns the work as a value, and the frontend controls
execution.

Also removes UpdatePromptMsg — the dispatch callback handles prompt
updates directly.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: note runner synchronization issue in TODO

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: fix stack overflow in updatePrompt/runShellAsync cycle

updatePrompt called runShellAsync which called updatePrompt, causing
infinite recursion. Fix by inlining the goroutine spawn for Prompt's
init function directly in updatePrompt, breaking the cycle.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: make Prompt pure, rename updatePrompt to syncPrompt

Prompt() now returns just a string — no async init function. LLM
initialization is solely handled by ReactToInput (the '>' key),
which already returns the init work. Prompt just renders current
state: 'loading...' if not initialized, model name if ready.

Rename updatePrompt → syncPrompt to reflect that it's a pure state
sync with no side effects. runShellAsync calls syncPrompt before
and after async work.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: replace editline with tuist TextInput, decouple from bubbletea

Hard cutover of the ShellHandler interface away from bubbletea types:

- ReactToInput: takes uv.KeyPressEvent directly instead of tea.KeyMsg
- Prompt: returns string only (no tea.Cmd)
- AutoComplete: returns tuist.CompletionResult instead of editline.Completions
- IsComplete: takes string instead of [][]rune
- Background: uses our own ExecCommand interface instead of tea.ExecCommand
- EncodeHistory/DecodeHistory: replaces editline.HistoryEncoder interface

Replace editline.Model with tuist.TextInput:
- TextInput handles its own key events natively (no UV→tea conversion)
- History navigation (up/down) implemented directly on frontendPretty
- CompletionMenu wired up via tuist.NewCompletionMenu
- Focus managed via tui.SetFocus instead of editline.Focus()/Blur()

Bubbletea remains only as an internal dependency for huh.Form, not
exposed in any interface.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: TextInput as TUI sibling with KeyInterceptor for special keys

- TextInput added to TUI container as sibling of frontendPretty,
  so cursor propagation works through tuist's container rendering
- TUI focus routes keys to TextInput when in insert mode
- KeyInterceptor handles ctrl+c/d, esc, history nav, mode switch
  before TextInput processes the key
- HandleKeyPress on frontendPretty only handles form and nav modes
- DecodeHistory made pure (no mode side-effect) fixing shell starting
  in prompt mode after history load
- go.mod: add replace directive for local tuist

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: enable hardware cursor when TextInput is added

SetShowHardwareCursor(true) is required for tuist to position and
show the terminal cursor. Without it, positionHardwareCursor always
hides the cursor even when a component returns a CursorPos.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: route completion menu keys through KeyInterceptor

CompletionMenu.HandleKeyPress is called first in the interceptor,
before history navigation or other special keys. When the menu is
visible, it consumes up/down/esc; otherwise they fall through to
history or TextInput.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: extract KeymapBar as a proper tuist component

KeymapBar is added to the TUI container after fe and before/after
the TextInput, so it renders at the bottom of the screen as a
sibling component rather than being manually concatenated in
fe.Render().

- New KeymapBar component in keymap.go with its own Render method
- Extracted RenderKeymap() as a standalone function (used by both
  KeymapBar and NotificationBubble)
- KeymapBar.PressedKey/PressedKeyAt updated via recordKeyPress()
  which also triggers keymapBar.Update() for re-render
- Removed renderKeymapLines, keymapView, renderKeymap from
  frontend_pretty.go
- TUI child order: fe → textInput → keymapBar

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: add children in natural order — output, prompt, keymap

Create and add keymapBar in startShell after textInput, instead of
adding it early in startTUI and shuffling with RemoveChild/AddChild.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: fix history round-trip — store raw encoded entries

History entries are stored with their mode prefix (e.g. '!' for shell,
'>' for prompt) throughout their lifecycle:
- Load: raw encoded entries from file, stored as-is
- Browse (up/down): DecodeHistory strips prefix for display
- Submit: EncodeHistory adds prefix before storing
- Save: written directly, already encoded

Previously, DecodeHistory was called at load time (stripping prefixes)
and EncodeHistory at save time (using current mode), which meant all
entries got re-encoded with whatever mode happened to be active at
save time, losing per-entry mode info.

Signed-off-by: Alex Suraci <alex@dagger.io>

* keymap: remove extra blank line between input and keymap

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: show keymap in all modes, not just shell

Create keymapBar in startTUI so it's visible for dagger call etc.
When startShell adds the textInput, it removes and re-adds the
keymapBar to maintain output → prompt → keymap order.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: update keymapBar when mode changes

Call keymapBar.Update() after editlineFocused changes in startShell,
enterNavMode, and enterInsertMode so the keymap shows the correct
bindings immediately.

Signed-off-by: Alex Suraci <alex@dagger.io>

* rm old .mds

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: guard against stale tree metadata in focus search

The childGapCounts/childLineCounts slices are populated during
SpanTreeView.Render, but findFocusInSubtree and totalLineCount
may be called before the tree has been rendered in the current
frame (e.g. when children changed). Guard against the length
mismatch instead of panicking.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: mark SpanTreeView dirty when children change

The root cause of the index-out-of-range panic: syncSpanTreeState
mutated st.children (via syncTreeNode) without calling st.Update(),
so tuist's renderComponent returned the cached result, skipping
Render() and leaving childGapCounts/childLineCounts stale from the
previous frame with a different children count.

Now syncTreeNode detects when children are added, removed, or
reordered and calls st.Update() to force a re-render. Also marks
dirty when collapsing (children cleared).

The length guard in findFocusInSubtree/totalLineCount is kept as
defense-in-depth.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: create TUI at construction time so it's never nil

Dispatch calls like SetPrimary, SetClient, etc. can be called before
startTUI (e.g. in the reportOnly path). Creating the TUI in NewWithDB
ensures fe.tui is always valid. startTUI just configures and starts it.

Signed-off-by: Alex Suraci <alex@dagger.io>

* idtui: use teav1.Wrap for huh.Form instead of manual bubbletea bridge

Replace the hand-rolled bubbletea command executor, message router, and
UV-to-tea key conversion with tuist's teav1.Wrap component. The form is
now a proper TUI child component that receives focus and key events
through tuist's standard routing.

This removes ~200 lines of duplicated bridge code:
- execTeaCmd / handleTeaMsg command executor
- uvKeyToTeaKeyMsg and all key conversion maps (uvToV1Key, shiftedKey,
  ctrlKeys, shiftKeys, ctrlShiftKeys)
- handleFormKey manual key forwarding
- promptDone message type
- Dead wrapCommand type (bubbletea era leftover)

Signed-off-by: Alex Suraci <alex@dagger.io>

* refactor(idtui): unify final render with tree-based rendering path

Replace renderProgressFinal's flat row loop with a recursive tree walk
that reuses renderTreeGap and renderRowContent with the SpanTreeView's
pre-computed prefixes. This eliminates the duplicate renderRowGap and
renderRow methods which duplicated the gap heuristic logic from
renderTreeGap.

Signed-off-by: Alex Suraci <alex@dagger.io>

* refactor(idtui): simplify notification overlays with a single Container

Replace per-notification overlay handles and manual stacking math with
a single tuist Container shown as one overlay. Notifications are added
as children of the container, and tuist handles vertical layout.

Also removes the sidebar background color and anchors notifications to
the top-right of the visible viewport instead of the bottom of the
content area.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix revealed connectors

Signed-off-by: Alex Suraci <alex@dagger.io>

* update gold - trailing whitespace gone

Signed-off-by: Alex Suraci <alex@dagger.io>

* chore(idtui): fix lint warnings

Fix gofmt formatting, consistent receiver names, remove unused
params/functions (logsDone, eofMsg, debug.go, prefix param).

Signed-off-by: Alex Suraci <alex@dagger.io>

* bump testdata to go 1.25.6

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): hide keymap bar on final render

Remove the keymap bar from the TUI before quitting so it doesn't
appear in the last rendered frame left on screen.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): add gap between huh form and keymap bar

Insert a blank line component between the form and the keymap bar
so they don't render flush against each other.

Signed-off-by: Alex Suraci <alex@dagger.io>

* switch to github tuist

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): use tuist Exec for background commands

Replace manual Stop/Start/pipe plumbing with a single tui.Exec call
that handles stdin passthrough and TUI lifecycle automatically.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): use pointer receiver for blankLine to avoid copylocks

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): address PR review comments

- Restore h.mode assignments in DecodeHistory so history navigation
  switches the input mode to match the history item.
- Restore LLM init in Prompt via async return value, matching the
  ReactToInput pattern. syncPrompt runs it via runShellAsync.
- Use fe.Update() instead of fe.Compo.Update().
- Remove unnecessary uvKeyString wrapper, inline k.String().
- Restore removed/oversimplified comments throughout rendering code:
  brailleDots descriptions, renderRollUpDots algorithm comments,
  statusIcon/renderToggler/renderStatusIcon docs, extractSpanContext
  explanations, findRollUpSpan walk comments, error origin guards,
  message span notes, effect span filtering, and more.

Signed-off-by: Alex Suraci <alex@dagger.io>

* refactor(idtui): use fe.Update() instead of fe.Compo.Update()

Signed-off-by: Alex Suraci <alex@dagger.io>

* refactor(idtui): remove line truncation, now handled by tuist

Tuist now truncates lines exceeding terminal width in its render
pipeline, so components no longer need to do it themselves.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): unfocus span highlight when editline is focused

When switching between nav and insert mode, recalculateViewLocked
so syncTreeNode detects the editlineFocused change and calls
st.Update() on the previously-focused span tree. Without this,
the span tree's cached render retains the focus highlight even
after the text input takes focus.

Also in shell mode, skip the below-focus truncation in
renderProgressLines so command output (inline logs) is not
clipped by the half-viewport afterBudget.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): remove extra blank line after LLM user prompt spans

In shell mode, renderTreeGap inserts a blank line between every
top-level span. LLM user prompts (█ block) already have their own
visual separation, so skip the gap for LLMRoleUser spans.

Signed-off-by: Alex Suraci <alex@dagger.io>

* clear input + bubbles on exit

Signed-off-by: Alex Suraci <alex@dagger.io>

* bump tuist

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): fix --progress=report hanging on tui dispatch

In reportOnly mode, the TUI event loop never starts, so
fe.tui.Dispatch() queues callbacks that are never consumed. This
caused all OTel exporters to silently drop data, and the metric
exporter to deadlock on a synchronous done channel.

Add fe.dispatch() helper that runs callbacks directly under a mutex
in reportOnly mode, falling back to fe.tui.Dispatch() when the TUI
is running. Remove the unnecessary synchronous done channel from
FrontendMetricExporter.Export.

Signed-off-by: Alex Suraci <alex@dagger.io>

* bump tuist, update usage

Signed-off-by: Alex Suraci <alex@dagger.io>

* TODO(split): fetch logs in dagger trace

Signed-off-by: Alex Suraci <alex@dagger.io>

* perf(idtui): use tuist Focusable for O(1) focus changes

Replace the O(N) tree walk in syncTreeNode that checked every node's
focus state on every keypress. SpanTreeView now implements
tuist.Focusable, so SetFocus dispatches SetFocused to exactly the 2
affected components (old and new focus).

focus() no longer calls recalculateViewLocked(), eliminating the full
RowsView rebuild and tree sync on navigation. This removes ~15s of
CPU time from the keypress hot path.

Also consolidate all focus mutations through focus() for consistency,
including nil support for unfocusing.

Signed-off-by: Alex Suraci <alex@dagger.io>

* perf(idtui): skip RowsView rebuild on expand/collapse

Make syncTreeNode and syncSpinnerTree read expanded/running state
from rowsView (TraceTree) instead of rows (TraceRow), eliminating
the dependency on the flat row list for tree sync.

setExpanded is now a pure setter. closeOrGoOut and openOrGoIn use
syncAfterExpandToggle which rebuilds only the flat row list from
the existing rowsView and syncs just the affected subtree. This
skips the expensive RowsView rebuild (WalkSpans + ShouldShow) and
fixes double-recalculate bugs where setExpanded and its callers
both called recalculateViewLocked.

Signed-off-by: Alex Suraci <alex@dagger.io>

* perf(idtui): O(depth) focus line lookup via bottom-up walk

Replace findFocusInSubtree which searched the entire tree top-down
(O(nodes)) with findFocusLine which walks up from the focused
SpanTreeView to the root (O(depth × siblings)). This is enabled by
adding parent and indexInParent pointers to SpanTreeView, set during
syncTreeNode and syncSpanTreeState.

Signed-off-by: Alex Suraci <alex@dagger.io>

* perf(idtui): coalesce recalculate and fix DB hot paths

Three independent fixes for the UI lockup on large traces:

1. Coalesce recalculateViewLocked to once per render frame. ExportSpans
dispatch callbacks now set a viewDirty flag instead of calling
recalculateViewLocked. Render checks the flag and recalculates once
before drawing. This prevents N batches × 25s recalculate from
starving the UI goroutine.

2. Make Activity.updateEarliest O(1) for the common case. When adding
a running span, just compare against current earliest instead of
scanning all running spans. Only rescan when removing the earliest.

3. Add visited set to PropagateStatusToParentsAndLinks. The CausalSpans
iterator walks transitively, so the same parent/causal span could be
visited many times via different paths. The visited set deduplicates,
reducing O(C×D) to O(C+D).

Signed-off-by: Alex Suraci <alex@dagger.io>

* fmt

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): use "space" instead of " " for space key matching

Ultraviolet Key.String() returns "space" for the space key, not
" ". The old literal space never matched, so pressing space did
nothing in nav mode.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): re-render span tree on expand/collapse toggle

When expanding a span with logs but no children, syncTreeNode
found no structural changes so the SpanTreeView was never marked
dirty. The logs (gated on row.Expanded) would not appear until
the next incidental re-render. Now syncAfterExpandToggle always
calls st.Update() on the toggled span.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): never crop view to less than terminal height

When the focused line was near the top, the bottom-truncation
logic could crop the visible content well below the viewport
height, leaving the screen half-empty. Now end is floored at
viewportHeight so the full terminal is always used.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: don't steal focus from input

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: fix moving through shell history

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: gap above assistant responses

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: center focused span in viewport

Rework progress cropping to keep the focused span fully visible
and fill remaining viewport space evenly above and below. This
prevents tall nodes (e.g. long LLM responses) from having their
bottom cut off when the top line was used as the anchor point.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: only crop bottom, not top

Content above the viewport scrolls into terminal scrollback
naturally. Only crop the bottom to keep the focused span within
the visible screen area, with remaining space split evenly above
and below.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: never crop bottom of focused span

Extract viewport cropping into a testable cropEnd function with
clear invariants: the focused span is always fully visible,
remaining viewport space is split evenly above and below, and
the viewport is filled when enough content exists.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): clear stale topTrees when rows become empty

recalculateViewLocked returned early when rows were empty without
calling syncSpanTreeState, leaving fe.topTrees stale from the
previous view. When SetPrimary switched from the engine span to
the shell span, renderProgressLines iterated the old topTrees and
rendered ghost spans (e.g. connect) above the prompt.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(dagui): race condition on TelemetryError field

TelemetryError was written from the OTel PeriodicReader error handler
goroutine and read from the render goroutine without synchronization.
Change it to atomic.Pointer[error] so access is safe across goroutines.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(dagui): move TelemetryError off FrontendOpts to fix copylocks

TelemetryError was an atomic.Pointer on FrontendOpts, which made the
struct non-copyable. FrontendOpts has value-receiver methods (e.g.
ShouldShow), so every call triggered a copylocks vet error. Move the
field to each Frontend implementation as a private telemetryError and
expose it via a SetTelemetryError interface method instead.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(dagui): print telemetry error warning in dots and logs frontends

Implement SetTelemetryError for the dots and logs frontends so they
store and print the warning on exit, matching the behavior of the
plain and pretty frontends. Also widen handleTelemetryErrorOutput to
accept TermOutput instead of *termenv.Output.

Signed-off-by: Alex Suraci <alex@dagger.io>

* feat(idtui): add Vim-style fulltext search

Add '/' search mode to the pretty TUI frontend. Searches span names
and vterm log content with case-insensitive substring matching. 'n'
and 'N' navigate forward/backward through matches (wrapping), 'Esc'
clears the active search.

Search matches are indicated with a yellow dot marker on matching
spans. Log matches scroll the vterm viewport to center the matched
line. Match state is refreshed automatically as new spans/logs arrive.

Includes SPEC.md with the full design rationale.

Signed-off-by: Alex Suraci <alex@dagger.io>

* feat(idtui): highlight all search matches with current-match indicator

Add ANSI-aware inline highlighting for search matches. All matches
get a yellow background, the current match gets bright yellow. Works
for both span name matches in tree lines and log content in vterms.

Vterm gains SearchQuery/SearchCurrentRow fields and SetSearchHighlight
to control per-line highlight rendering. SpanTreeView self-lines are
post-processed with highlightANSI after rendering.

dirtySearchTrees tracks previous vs current match spans and calls
Update on all affected SpanTreeViews so tuist repaints them when
search state changes (next/prev/clear/new query).

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): fix search highlight bleeding to end of line, swap colors

Two fixes:

1. The highlight was extending to end of line because hlEnd used a
   full ANSI reset which cleared ALL formatting (faint, bold, etc.),
   not just the highlight's bg/fg. After the reset, only the last
   single ANSI sequence was restored, losing cumulative state like
   faint text. Now we accumulate ALL ANSI sequences seen outside of
   highlights and replay them all after hlEnd to fully restore the
   prior formatting state.

2. Swap colors per feedback: non-current matches use white background,
   current/focused match uses yellow background. Both use black fg.

Adds unit tests for the ANSI-aware highlighting logic covering plain
text, case insensitivity, formatting preservation across highlights,
multiple sequences, match-at-end, and multiple matches.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): split title vs log rendering to prevent double-highlighting

SpanTreeView.Render was applying highlightANSI to ALL self lines,
including vterm log output that already had its own search highlights
applied. This caused double-highlighting with garbled ANSI output.

Split renderRowContent into renderStep (title) + renderRowContentRest
(logs/errors/debug). SpanTreeView.Render now highlights only the
title lines; log highlighting is handled by Vterm's SearchQuery.

Signed-off-by: Alex Suraci <alex@dagger.io>

* docs: update SPEC.md with midterm-native search plan

Document what's done, what's broken (ANSI post-processing for vterm
highlights), and the plan to move search highlighting into midterm
itself where it operates on structured rune content + format canvas
rather than post-processing ANSI byte streams.

Signed-off-by: Alex Suraci <alex@dagger.io>

* docs: update search SPEC with implementation progress

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): clone ResourceMetrics before async dispatch

The OTel PeriodicReader pools and reuses ResourceMetrics structs
via sync.Pool. FrontendMetricExporter.Export dispatches a closure
to the TUI goroutine asynchronously, so by the time it runs the
SDK may already be writing to the same struct for the next
collection cycle. Clone the data before dispatching, matching the
pattern already used for spans and logs.

Signed-off-by: Alex Suraci <alex@dagger.io>

* feat(idtui): use midterm native search highlighting

Replace ANSI post-processing in Vterm.Render with midterm's native
search overlay. SetSearchHighlight now delegates to midterm's Search()
and SearchSetCurrent() which operate on the rune/column canvas,
fixing byte-vs-rune drift with multi-byte characters.

searchVtermRows reads midterm's SearchMatchRows() instead of
independently scanning content, and syncVtermSearchHighlights runs
before buildSearchMatches so midterm has fresh results to read.

Signed-off-by: Alex Suraci <alex@dagger.io>

* feat(idtui): search all spans in tree, not just visible rows

Walk the full RowsView trace tree (subject to verbosity) when building
search matches, so collapsed/hidden spans are included. Vterm content
searches run in parallel. Navigating to a match in a collapsed span
expands its ancestors to reveal it.

Previously only fe.rows.Order (the flattened visible list) was searched,
so matches deep in the trace were invisible until manually expanded.

Signed-off-by: Alex Suraci <alex@dagger.io>

* feat(idtui): deselect search match on focus change

Moving focus (arrow keys, clicking) clears the current search match
selection so that n/N seek relative to the new focus position rather
than continuing from the old match. N seeks backward from focus.

Also: fix cropping behavior once again, this time to avoid placing the
parent flush with the top edge.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): don't clear search selection during search navigation

goToSearchMatch calls focus() which was clearing searchIdx. Add a
searchNavigating guard so focus() only resets the search selection
on manual focus changes (arrow keys), not when search is driving
the navigation.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): don't steal focus from search input

focus() now skips SetFocus when searchActive is true, preventing
autoFocus and recalculateViewLocked from stealing TUI input focus
away from the search prompt while the user is typing a query.

Also guard enterSearchMode against duplicate calls so a second /
keystroke during an active search doesn't install a second prompt.

Signed-off-by: Alex Suraci <alex@dagger.io>

* perf(idtui): simplify search refresh, drop goroutines

With midterm's incremental search, the per-tick cost is proportional
to new output, not total content. Drop the goroutine-per-span fan-out
(SearchMatchRows is just a slice read) and eliminate the redundant
double call to syncVtermSearchHighlights in the refresh path.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): refresh search results on every frame

Search refresh was only triggered by recalculateViewLocked (gated on
viewDirty), which is set by ExportSpans but not by log writes. New
log output containing matches wouldn't appear until a span export or
manual focus change happened to trigger a recalculate.

Move the search refresh to Render, running every frame when a search
is active. With midterm's incremental search this is cheap — only
rows that received new Write() data since the last frame are
re-scanned.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): update keymap bar when search matches change

refreshSearchMatches wasn't calling keymapBar.Update(), so the
match count display (e.g. '3/52') didn't update when new log
output added matches.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fmt

Signed-off-by: Alex Suraci <alex@dagger.io>

* refactor(idtui): move debugged state to SpanTreeView

Migrate fe.debugged from a single SpanID on frontendPretty to a
per-component bool on SpanTreeView. The ? key handler now looks up
the focused SpanTreeView, toggles its debugged field, and calls
Update() to re-render. This also allows multiple spans to be
debugged simultaneously.

Signed-off-by: Alex Suraci <alex@dagger.io>

* cleanup SPEC

Signed-off-by: Alex Suraci <alex@dagger.io>

* fmt

Signed-off-by: Alex Suraci <alex@dagger.io>

* lint

Signed-off-by: Alex Suraci <alex@dagger.io>

* fixup linebreak

Signed-off-by: Alex Suraci <alex@dagger.io>

* bump module go.mods

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): save shell history before clearing shell state

stopShell() was setting fe.shell and fe.textInput to nil before Run()
had a chance to persist the history file. The save condition in Run()
always evaluated to false, so history was never written to disk. Move
the save into stopShell() where it runs before the state is cleared.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): return correct byte count from multiprefixw Write

Write was returning len(p) after the loop consumed all bytes, so it
always returned 0. Return the original length instead.

Signed-off-by: Alex Suraci <alex@dagger.io>

* feat(idtui): show error message above prompt when prompt turns red

Add an ErrorLabel component that displays the underlying error message
when a shell command fails. Previously the only signal was the prompt
character turning red, with no indication of what went wrong. Now the
error text is rendered as a red line between the output area and the
prompt input, and cleared when the next command is submitted.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(tui): fix janky focus

Signed-off-by: Alex Suraci <alex@dagger.io>

* feat(idtui): word-wrap ErrorLabel to fit terminal width

Long error messages now wrap at word boundaries instead of being
truncated or overflowing. Continuation lines are indented to align
with the text after the "Error: " prefix.

Signed-off-by: Alex Suraci <alex@dagger.io>

* refactor(idtui): migrate from lipgloss v1 to v2

Replace github.com/charmbracelet/lipgloss with charm.land/lipgloss/v2.
Use v2 named color constants (lipgloss.Red, lipgloss.BrightBlack, etc.)
instead of lipgloss.Color("1") or lipgloss.ANSIColor(termenv.ANSI*).
Replace removed SetHasDarkBackground/HasDarkBackground() with the v2
HasDarkBackground(in, out) API, keeping env-var overrides.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fmt

Signed-off-by: Alex Suraci <alex@dagger.io>

* add tuist deps to go.mod/go.sum

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): break syncPrompt/runShellAsync mutual recursion

runShellAsync called syncPrompt synchronously before spawning its
goroutine. When the LLM session isn't loaded yet, syncPrompt returns
an init func, which calls runShellAsync, which calls syncPrompt
again, overflowing the stack. Remove the synchronous syncPrompt call
so the cycle is broken; the async goroutine still dispatches a
syncPrompt on completion.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): sync prompt when cycling through shell history

DecodeHistory correctly updates the internal mode based on the !/>
prefix, but the UI prompt was never refreshed to reflect the mode
change. Now SaveBeforeHistory/RestoreAfterHistory are exposed on the
ShellHandler interface, and syncPrompt is called after each history
navigation so the prompt matches the history entry's mode.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): replace stale focusedIdx with focusedIndex()

The focusedIdx field was set when focus() was called but never
updated when expand/collapse rebuilt the row list.

NOTE: the assumption was that this caused search next/prev to compare
against a stale index, jumping to matches before the focused item when
collapsed trees shifted indices. But it didn't help. This refactor is
still useful nonetheless, so keeping.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix(idtui): skip collapsed matches before cursor in search navigation

When pressing n to find the next search match, hidden spans inside
collapsed subtrees were treated as valid candidates regardless of
their position. A match nested inside a collapsed tree before the
cursor would be selected immediately. Now matchRowIndex resolves
hidden spans to their nearest visible ancestor's row index so the
forward/backward comparison works correctly.

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: rm wonky optimization

it's not clear this was needed

Signed-off-by: Alex Suraci <alex@dagger.io>

---------

Signed-off-by: Alex Suraci <alex@dagger.io>
* vault OIDC implementation

Signed-off-by: Nipuna perera <nipuna.royal@gmail.com>

* Copy skills for github copilot

Signed-off-by: Nipuna perera <nipuna.royal@gmail.com>

* add integration tests

Signed-off-by: Nipuna perera <nipuna.royal@gmail.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Nipuna Perera <nipuna.royal@gmail.com>
Signed-off-by: Nipuna perera <nipuna.royal@gmail.com>

* add healthchecks for tests

Signed-off-by: Nipuna perera <nipuna.royal@gmail.com>

---------

Signed-off-by: Nipuna perera <nipuna.royal@gmail.com>
Signed-off-by: Nipuna Perera <nipuna.royal@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@vito vito force-pushed the workspace-plumbing-dagql-dagql branch from 209c17a to abfd53e Compare March 18, 2026 03:44
sipsma and others added 17 commits March 18, 2026 11:31
It seems that it's still possible in some cases for a contextual
directory sourced from a private git repo to end up in function cache in
a state where a client hitting the cache reloads it but with different
credentials than before and then the engine complains it doesn't have
the creds anymore (since the http auth secret becomes and explicit arg
to the git operation).

All these problems are going away in the new cache implementation, but
in the meantime we can patch this by just checking whether the creds are
valid and, if not, reloading them again as though they weren't set.

Signed-off-by: Erik Sipsma <erik@sipsma.dev>
…ons (dagger#11981)

When set to any non-empty value, DAGGER_NO_UPDATE_CHECK disables the
automatic version check that runs on every CLI invocation. This is
useful in CI environments and automated scripts where upgrading is
managed externally (e.g., pinned versions in Dockerfiles, Nix, Homebrew)
and the update notification is unnecessary noise on stderr.

This follows the same pattern used by other CLI tools such as
GH_NO_UPDATE_NOTIFIER (GitHub CLI) and CHECKPOINT_DISABLE (Terraform).

Fixes dagger#11979

Signed-off-by: Jaredw2289-svg <Jaredw2289-svg@users.noreply.github.com>
Signed-off-by: junyuw2289-svg <junyuw2289@gmail.com>
Bumps the sdk-python-docker group with 1 update in the /modules/ruff/build directory: [astral-sh/ruff](https://github.com/astral-sh/ruff).


Updates `astral-sh/ruff` from 0.15.4 to 0.15.5
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](astral-sh/ruff@0.15.4...0.15.5)

---
updated-dependencies:
- dependency-name: astral-sh/ruff
  dependency-version: 0.15.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: sdk-python-docker
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dagger#12005)

Bumps the sdk-typescript group in /sdk/typescript with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.3.5` | `25.5.0` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.56.1` | `8.57.0` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.56.1` | `8.57.0` |
| [rollup-plugin-dts](https://github.com/Swatinem/rollup-plugin-dts) | `6.3.0` | `6.4.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.56.1` | `8.57.0` |


Updates `@types/node` from 25.3.5 to 25.5.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@typescript-eslint/eslint-plugin` from 8.56.1 to 8.57.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.56.1 to 8.57.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.0/packages/parser)

Updates `rollup-plugin-dts` from 6.3.0 to 6.4.0
- [Changelog](https://github.com/Swatinem/rollup-plugin-dts/blob/master/CHANGELOG.md)
- [Commits](Swatinem/rollup-plugin-dts@v6.3.0...v6.4.0)

Updates `typescript-eslint` from 8.56.1 to 8.57.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: sdk-typescript
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.57.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: sdk-typescript
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.57.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: sdk-typescript
- dependency-name: rollup-plugin-dts
  dependency-version: 6.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: sdk-typescript
- dependency-name: typescript-eslint
  dependency-version: 8.57.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: sdk-typescript
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore: bump google.golang.org/grpc v1.79.1 → v1.79.3

Fixes GHSA-p77j-4mvh-x3m3.

Fixes dagger#12024

Signed-Off-By: Yves Brissaud <yves@dagger.io>

* chore: bump github.com/buger/jsonparser v1.1.1 → v1.1.2

Signed-off-by: Yves Brissaud <yves@dagger.io>

---------

Signed-off-by: Yves Brissaud <yves@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
* chore: bump internal tooling to v0.20.3

Signed-off-by: Erik Sipsma <erik@sipsma.dev>

* also run workaround on .dagger

Signed-off-by: Erik Sipsma <erik@sipsma.dev>

---------

Signed-off-by: Erik Sipsma <erik@sipsma.dev>
Update the go_sdk to send a pinned lib version to a commit from dagger-go-sdk.

⚠️ This change the release process, now we will need to update the const in go_sdk to the latest commit of dagger-go-sdk, this commit will not be the one tagged because it's done before the release.
At some point, it would be much better to release the SDKs before the engine so we can set the commit of the released instead of the one before.

Signed-off-by: Tom Chauveau <tom@dagger.io>
* fix aws:// secrets uri in docs

Signed-off-by: kpenfound <kyle@dagger.io>

* generate

Signed-off-by: kpenfound <kyle@dagger.io>

---------

Signed-off-by: kpenfound <kyle@dagger.io>
* refresh on docs cloud page

Signed-off-by: kpenfound <kyle@dagger.io>

* improve phrasing in dagger cloud section on overview page

Signed-off-by: kpenfound <kyle@dagger.io>

* dont promote daggerverse as much until we improve it for modules v2

Signed-off-by: kpenfound <kyle@dagger.io>

* remove unused screenshots

Signed-off-by: kpenfound <kyle@dagger.io>

---------

Signed-off-by: kpenfound <kyle@dagger.io>
Signed-off-by: Solomon Hykes <solomon@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
Signed-off-by: Solomon Hykes <solomon@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
vito added 13 commits March 26, 2026 21:26
When a module constructor has no doc comment, the constructor field on
Query gets an empty description. Fall back to the module description so
that dependency constructors carry a meaningful description in the shell
and schema.

Signed-off-by: Alex Suraci <alex@dagger.io>
With entrypoint proxying, missing constructor args are reported by
dagql as 'missing required argument' instead of the shell's own
positional argument validation.

Signed-off-by: Alex Suraci <alex@dagger.io>
The shell now starts at Query, so all core functions are already
accessible directly. The .stdlib namespace is redundant and can be
removed along with all its supporting code: the stdlib command list,
StdlibCommand/Stdlib/StdlibHelp methods, IsStdlib/NewStdlibState
state helpers, completion handlers, and associated tests.

Signed-off-by: Alex Suraci <alex@dagger.io>
Same reasoning as .stdlib: the shell starts at Query, so all core
functions are already directly accessible. The .core namespace for
accessing load-*-from-id and other unpromoted functions is no longer
needed.

Signed-off-by: Alex Suraci <alex@dagger.io>
These internal functions are still callable but are filtered from the
help listing to reduce clutter.

Signed-off-by: Alex Suraci <alex@dagger.io>
Now that the shell always reflects Query directly:

- Remove GetCoreFunctions/GetCoreFunction/HasCoreFunction (dead code)
- Remove CmdType/CmdFunction from CompletionContext (only used by
  .stdlib/.core namespace completion)
- Remove GetModuleDef; callers use GetDef + HasModule() instead
- Fix StateLookup and entrypointCall to find functions via GetDef
  so core functions work even without a module loaded
- Fix root completions to use GetDef instead of GetModuleDef

Signed-off-by: Alex Suraci <alex@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
Functions are now grouped into sections:
- Available Functions: core API functions
- <module> — <description>: entrypoint module functions
- Installed Modules: dependency module constructors
- Builtins: shell builtins

Signed-off-by: Alex Suraci <alex@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
The entrypoint proxy's `with` field now carries the original
constructor's doc comment instead of an auto-generated description.
This is the user-facing constructor so it should have their docs.

ModuleDoc and the type doc path also now resolve the module's named
main object when entrypoint proxying is active, so `.help .` shows
the original constructor info and `. | .help` shows the module's
object type instead of Query.

Signed-off-by: Alex Suraci <alex@dagger.io>
Merge ServedMods into ModDeps, eliminating the indirection between the
two types. ModDeps now tracks per-module InstallOpts (SkipConstructor,
Entrypoint) directly and handles the inner/outer server split for
entrypoint proxies.

The merged type is fully immutable: Append, Prepend, With, and Clone
all return new instances. The mutable Add + invalidateCache pattern
from ServedMods is replaced by immutable With + pointer reassignment
in session.go. Schema caching is computed once per instance.

The public Mods field is replaced by a Mods() accessor, and the
Lookup method absorbs LookupDep.

Signed-off-by: Alex Suraci <alex@dagger.io>
Rename NewModDeps to NewSchemaBuilder and update all call sites.

Collapse the confusing .Schema() and .Server() methods into a single
.Server() method. The old .Server() returned an inner dagql server
without entrypoint proxies for ID loading, but the outer server
already delegates ID loading via its IDLoader hook, making the
separate method unnecessary. The lazilyLoadedInner field is removed.

Signed-off-by: Alex Suraci <alex@dagger.io>
@vito vito force-pushed the workspace-plumbing-dagql-dagql branch from 37d1edf to 4235057 Compare March 27, 2026 02:39
vito added 16 commits March 26, 2026 22:54
Replace the public IDLoader func and Inner *Server fields on
dagql.Server with a private canonical field and a Canonical() method.

Canonical() returns the server without entrypoint sugar. For servers
with entrypoint proxies, this is the underlying server where
constructors and core fields live unshadowed. For all other servers
it returns the receiver itself.

Load and LoadType now delegate to Canonical() directly, eliminating
the IDLoader indirection. Proxy resolvers and SDK plumbing use
dag.Canonical() instead of checking dag.Inner.

Signed-off-by: Alex Suraci <alex@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
When installing an explicit constructor on the Query root, the engine
skipped the object's description in its fallback chain, going straight
from the constructor's doc comment to the module description. This left
constructors with no description when only the object type had a doc
comment, forcing a CLI-side workaround in functionListRun.

Add objDef.Description to the explicit constructor fallback chain,
matching the no-constructor path (constructor → object → module), and
remove the now-unnecessary CLI workaround.

Signed-off-by: Alex Suraci <alex@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
now a client param

Signed-off-by: Alex Suraci <alex@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
…#11949

Restore the spirit of PR dagger#11949's changes to moddeps.go, adapted to
the SchemaBuilder type:

- buildSchema and lazilyLoadSchema no longer generate or cache the
  introspection JSON file. Only the dagql.Server is cached.

- SchemaIntrospectionJSONFile generates JSON on-demand via
  dag.Select(__schemaJSONFile, ...). The dagql CachePerSchema
  mechanism handles per-args caching, so different hiddenTypes
  produce correctly different results without separate lazy caching.

- SchemaIntrospectionJSONFileForModule now hides both
  TypesToIgnoreForModuleIntrospection and TypesHiddenFromModuleSDKs
  (Engine, EngineCache, etc.), since the caller decides what to hide.

- New SchemaIntrospectionJSONFileForClient for standalone client
  generation, which passes no hidden types so Engine and other types
  remain visible.

- getSchemaJSON in schema/query.go no longer unconditionally scrubs
  TypesHiddenFromModuleSDKs — that responsibility moves to the
  callers (SchemaIntrospectionJSONFileForModule passes them as
  hiddenTypes).

Signed-off-by: Alex Suraci <alex@dagger.io>
Reflect the consolidation of ServedMods/ModDeps into SchemaBuilder,
the replacement of IDLoader/Inner with Canonical(), and the
on-demand introspection JSON generation.

Signed-off-by: Alex Suraci <alex@dagger.io>
Signed-off-by: Alex Suraci <alex@dagger.io>
Instead of post-filtering in currentTypeDefs, the outer dagql server
now genuinely omits core API fields from Query when HideCoreAPI is
set. SchemaBuilder gains a hideCoreAPI flag that forces the inner/outer
server split and strips core Query fields from the outer server after
building. Core types (Container, Directory, etc.) remain in the schema
for return-type resolution; only the Query-level entry points like
container(), directory(), git(), etc. are removed.

Bootstrapping fields (currentTypeDefs, currentModule,
currentFunctionCall, version) are retained on the outer server.
The CLI's ShouldSkipFunction list is updated to also hide
currentTypeDefs and version from dagger functions output.

Signed-off-by: Alex Suraci <alex@dagger.io>
The workspace module loader selects currentWorkspace from Query root
during function resolution, so it must survive the HideCoreAPI strip.

Signed-off-by: Alex Suraci <alex@dagger.io>
Resolvers that call CurrentDagqlServer(ctx) and select from Root need
the full core schema, not the stripped outer server. Instead of fixing
each call site individually (18+ sites select from Root), put the
canonical server into context during field resolution. Canonical()
returns self when no inner/outer split exists, so this is a no-op for
single-server setups.

This reverts the per-call-site .Canonical() calls in modfunc.go and
the currentWorkspace bootstrap keep-list entry, replacing them with
the structural fix.

Signed-off-by: Alex Suraci <alex@dagger.io>
The CLI uses Query.address to resolve user-supplied flag values
(containers, directories, files, secrets, services) via the Go SDK
client, which sends GraphQL queries to the outer server.

Signed-off-by: Alex Suraci <alex@dagger.io>
The version field is used by 'dagger core version' which doesn't set
HideCoreAPI. Hiding it in ShouldSkipFunction broke command resolution.
Remove it from both the bootstrap keep list and ShouldSkipFunction —
it gets stripped from the outer server naturally (no Module set), and
remains available on the core-only server where dagger core runs.

Signed-off-by: Alex Suraci <alex@dagger.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.