Skip to content

Improve message markdown display and formatting - #7

Merged
wesbillman merged 1 commit into
mainfrom
wesb/improve-message-markdown
Mar 9, 2026
Merged

Improve message markdown display and formatting#7
wesbillman merged 1 commit into
mainfrom
wesb/improve-message-markdown

Conversation

@wesbillman

Copy link
Copy Markdown
Collaborator

Summary

  • render message bodies with a shared markdown component using react-markdown, GFM, and line-break support
  • add styled markdown output for links, code, tables, quotes, and images in the message timeline
  • move the message composer and timeline into features/messages and remove the old static chat data

Testing

  • pre-push lefthook ran automatically during git push
  • cargo fmt --all -- --check
  • pnpm check
  • pnpm build
  • cargo check --manifest-path desktop/src-tauri/Cargo.toml
  • cargo clippy --workspace --all-targets -- -D warnings
  • ./scripts/run-tests.sh unit

@wesbillman
wesbillman merged commit 774bd62 into main Mar 9, 2026
7 checks passed
@wesbillman
wesbillman deleted the wesb/improve-message-markdown branch March 9, 2026 23:34
tlongwell-block added a commit that referenced this pull request Mar 10, 2026
* origin/main:
  Add desktop Home feed (#12)
  Add desktop Playwright e2e harness (#11)
  Update desktop icon and persist window state (#9)
  feat: add channel creation flow (#8)
  Improve message markdown display and formatting (#7)
  feat(desktop): connect chat to relay (#6)
  docs(readme): clarify desktop setup (#4)
  feat: add desktop app (#3)

# Conflicts:
#	crates/sprout-test-client/tests/e2e_rest_api.rs
tlongwell-block added a commit that referenced this pull request Mar 16, 2026
Crossfire round 1: codex 4/10, opus 8/10. All critical issues fixed:

Security (critical):
- Force channel_id=None for kind:1059 gift wraps — prevents channel-scoped
  storage that would bypass #p AUTH-gating (codex finding #1)

Correctness:
- NIP-50 pagination loop — keep fetching Typesense pages until limit met
  or result set exhausted, capped at MAX_SEARCH_PAGES=5 (codex finding #2)
- Push authors/since/until to Typesense filter_by — post-filtering is now
  a correction step, not the primary filter (codex + opus suggestion)
- NIP-10 root tag validation — reject events where client-supplied root
  diverges from server-resolved ancestry (codex finding #3)

Clarity:
- Consolidate #p gating into single P_GATED_KINDS check (opus suggestion #7)
- filter.clone() → std::slice::from_ref(filter) (opus suggestion #1)
- Remove no-op get_events_by_ids test, add debug_assert (opus #3, #5)
tlongwell-block added a commit that referenced this pull request May 14, 2026
Adds a Settings → Agent Provider panel to the desktop GUI for configuring
the `sprout-agent` provider, model, API key, and behavior knobs. Settings
are encrypted at rest with NIP-44 self-encryption (the user's own nostr
key) and injected into the sprout-agent child's env at spawn time. The
panel also gains automatic provider-URL detection based on the API key
format the user pastes.

## Backend (`desktop/src-tauri/src/commands/agent_provider_settings/`)

- `mod.rs` — IPC types + plaintext `StoredSettings` (Drop zeroizes
  `api_key`; no Debug derive on input/stored). v2 envelope binds the
  plaintext to its owner pubkey for rollback / envelope-swap protection.
- `storage.rs` — envelope read/write, NIP-44 encrypt/decrypt with
  `Zeroizing<String>` for plaintext, atomic-rename writes, file-size
  cap, `normalize_origin` (rejects non-loopback `http://`, userinfo,
  query, fragment), `validate_input` (provider whitelist, control-char
  rejection, size caps for key/model/base_url/system_prompt, positive-
  int knobs). `validate_stored` mirrors the same rules on the decrypted
  blob at spawn time — fails closed on a rolled-back pre-validation
  envelope so a redirected `http://api.example.com/v1` cannot escape.
- `commands.rs` — `get_*`, `save_*`, `delete_*`, `get_*_env_presence`
  Tauri commands. The save command trims whitespace + zeroes the input
  api_key before validation can early-return.
- `spawn.rs` — `LoadForSpawn` enum + `EnvPairs` newtype whose Drop
  zeroizes every value buffer. `apply_to_command` hands each env pair
  to `Command::env` by reference, zeroizing the local buffer after.
  Spawn policy: Ok → strip OWNED_AGENT_ENV_VARS + ACP-level vars then
  inject; None → no-op; IdentityMismatch / Error → fail closed (strip
  inherited owned vars, inject nothing).
- `tests.rs` — round-trip envelope I/O, identity-rotation, save-time
  validation (oversized prompt, zero timeouts, tiny history bytes,
  unknown provider, control chars, oversized fields, api-key whitespace
  trim, owner_pubkey v2), `apply_to_command` × `LoadForSpawn` matrix
  (Ok/None/IdentityMismatch-fails-closed/Error-fails-closed, openai
  dialect), `stored_to_env_pairs` for each dialect, R7 `validate_stored`
  coverage (non-loopback http, control chars in key/model/base_url,
  userinfo/query in base_url, unknown provider, oversized prompt,
  empty-key + loopback local accepted).

## Runtime integration (`desktop/src-tauri/src/managed_agents/runtime.rs`)

- `build_agent_command` calls `agent_provider_settings::apply_to_command`
  exactly when the harness is `sprout-agent`. ACP-level vars
  (SPROUT_AGENT_PROVIDER etc.) are stripped from inherited parent env
  before injection so a stale shell `ANTHROPIC_API_KEY` never shadows
  saved settings. `respond-to` gate env (`SPROUT_ACP_RESPOND_TO[_ALLOWLIST]`)
  threads through with the new `owner_hex: Option<&str>` parameter
  (origin/main merge).

## Frontend

- `lib/detectProvider.ts` — pure key-format detector. Recognizes
  Anthropic, OpenAI (legacy/proj/svcacct via fixed infix), OpenRouter,
  Groq, xAI, Cerebras, Together, Perplexity, Fireworks (medium), bare
  sk- → DeepSeek (low + ambiguity-aware), plus localhost/127.0.0.1
  patterns for Ollama / vLLM / llama.cpp. Includes ADMIN_ONLY_PROVIDER_ID
  sentinel for `sk-ant-admin01-` which we explicitly refuse to save.
  Key format wins over a prefilled default base URL; an explicit non-
  default base URL wins back for medium-confidence keys (e.g. Fireworks
  + api.openai.com host). Fixture strings construct the OpenAI infix
  via concat so GitHub's secret scanner doesn't regex-match an inline
  OpenAI-shaped service-account/project key (`detectProvider.test.mjs`,
  `settings-agent-provider.spec.ts`).
- `lib/providerCatalog.ts` — declarative catalog (id, label, dialect,
  isLocal, default model + base URL, key-shape hint). Drives the
  picker, the auto-fill on detection, the local-provider placeholder
  enforcement, and the per-provider model field default.
- `lib/agentProviderFormState.ts` — FormState shape + reducers.
  `applyProviderSwitch` is the single source of truth for what gets
  reset on a provider change (model when empty or still previous
  default; baseUrl when new provider has a default OR user hasn't
  edited it; clears previous default host for null-default providers;
  drops apiKey on switch TO a local provider). Used by both the
  manual picker and the auto-detect effect so the policy can't drift.
- `lib/agentProviderSettingsApi.ts` + `hooks/useAgentProviderSettings.ts`
  — typed IPC wrappers + React-Query hooks (load / save / delete /
  envPresence).
- `ui/AgentProviderSettingsCard.tsx` — the panel itself. Empty state
  with shell-env hint, identity-rotation banner, load-error banner,
  detected-provider badge, reveal/hide toggle, advanced section,
  inline provider-change warning, confirm-clear dialog. On save success
  the plaintext is wiped from form state + reveal toggles off,
  independent of any React-Query refresh effect (covers the structural-
  sharing identical-redacted-view edge case).
- `ui/AgentProviderAdvancedFields.tsx`, `AgentProviderBanners.tsx`,
  `AgentProviderClearDialog.tsx` — split components.

## Per-agent dialog (sprout-agent special case)

- `agents/ui/CreateAgentDialogSections.tsx` — Model + System prompt
  inputs are hidden for sprout-agent paths (those are owned globally).
  A note line points users to Settings → Agent Provider.
- `agents/ui/EditAgentDialog.tsx` — passes `selectedProviderId="custom"`
  to the shared runtime fields so the agent-command input stays
  editable for existing rows; the system-prompt hide still resolves
  via `isSproutAgentPath`'s `agentCommand` arm.
- `agents/ui/ManagedAgentRow.tsx` — "Model managed by Sprout settings"
  link is a span with role="button" + stopPropagation (was a nested
  `<button>` inside the row button — both invalid HTML and double-
  triggering).
- `agents/lib/resolveAcpProviderId.ts` — TS / Rust alignment for
  inline-args resolution (Rust `known_acp_provider` strips args; TS
  now matches).

## Tests

- Rust: 328 tests passing (R7 added 7; full agent_provider_settings
  suite at 44/44).
- TS / node-test: 55 cases for the form-state reducer + provider
  detector.
- Playwright integration: 12 settings-agent-provider scenarios (empty
  state + detection + save round-trip, identity-rotation banner,
  provider-change-warning, key-format-beats-prefilled-baseUrl, load-
  error banner, clear flow + Escape cancel, rotation banner a11y,
  local-provider switch with saved key, manual switch reset, detected-
  provider model reset, post-save key-input clear).

## just ci summary

- Rust: 321/321 + 7 new validate_stored tests
- Mobile: 336/336
- Desktop / web: format + biome + file-size + ts-check all green
- Playwright integration agents + settings-agent-provider: 19/19 green

## Codex review history

- Reviews #1, #2, #3, #5, #6 surfaced and fixed: non-loopback HTTP at
  save AND spawn, identity-mismatch fails closed, local-provider key
  leak prevention, inline-args resolver alignment, Zeroize on api_key
  before early-return, no Debug derive, control-char / length caps +
  trim, owner_pubkey v2 envelope, local-provider switch unblock,
  EditAgentDialog command field, model reset on detection switch,
  nested-button fix, `applyProviderSwitch` reducer extraction.
- Review #7 (P2-UI + P2-Rust): clear form.apiKey + revealKey on save
  success; validate decrypted settings at spawn time.
- Review #8: 9/10, no blocking findings.

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
wpfleger96 added a commit that referenced this pull request May 22, 2026
…iew findings

The original implementation created a second parallel Tauri command
(discover_all_acp_providers) alongside the existing one to avoid
changing the return type. This produced two commands, two hooks, two
query keys, and two raw type converters. Consolidates into a single
command returning the full catalog, with a useAvailableAcpProviders
hook that type-narrows for callers needing non-null command/binaryPath.

Also fixes: pipe deadlock in install command (#1), UTF-8 truncation
panic (#2/#4), adds install concurrency guard (#11), exact provider ID
match (#15), error display stdout fallback (#5), success banner
suppression when already available (#12), misleading re-run text (#13),
IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7),
configurable e2e mocks (#9), shared raw type exports (#8), and
classify_provider unit tests (#10).
wpfleger96 added a commit that referenced this pull request May 22, 2026
…iew findings

The original implementation created a second parallel Tauri command
(discover_all_acp_providers) alongside the existing one to avoid
changing the return type. This produced two commands, two hooks, two
query keys, and two raw type converters. Consolidates into a single
command returning the full catalog, with a useAvailableAcpProviders
hook that type-narrows for callers needing non-null command/binaryPath.

Also fixes: pipe deadlock in install command (#1), UTF-8 truncation
panic (#2/#4), adds install concurrency guard (#11), exact provider ID
match (#15), error display stdout fallback (#5), success banner
suppression when already available (#12), misleading re-run text (#13),
IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7),
configurable e2e mocks (#9), shared raw type exports (#8), and
classify_provider unit tests (#10).
wpfleger96 added a commit that referenced this pull request May 22, 2026
…iew findings

The original implementation created a second parallel Tauri command
(discover_all_acp_providers) alongside the existing one to avoid
changing the return type. This produced two commands, two hooks, two
query keys, and two raw type converters. Consolidates into a single
command returning the full catalog, with a useAvailableAcpProviders
hook that type-narrows for callers needing non-null command/binaryPath.

Also fixes: pipe deadlock in install command (#1), UTF-8 truncation
panic (#2/#4), adds install concurrency guard (#11), exact provider ID
match (#15), error display stdout fallback (#5), success banner
suppression when already available (#12), misleading re-run text (#13),
IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7),
configurable e2e mocks (#9), shared raw type exports (#8), and
classify_provider unit tests (#10).
DevYonghunT added a commit to DevYonghunT/buzz that referenced this pull request Aug 4, 2026
완료 기준 7개 중 부분 충족이던 셋(block#3 renamed, block#6 upgrade 경로, block#7 UI 실행
증거)을 충족으로 올리고 Phase 3을 완료로 표시한다. 세션 D 시점의 판정은 각
행에 그대로 남겼다 — 표를 고치고 근거를 나중에 맞추는 순서가 아니다.

WORKSPACE_CATALOG.md의 두 곳을 정정했다. §7의 renamed 「구현 상태」는 이제
미리보기와 ledger 둘 다이고, 검증 표의 「데스크톱 | pnpm test | 설정 카드
렌더」는 애초에 없던 테스트를 가리키고 있었으므로 실제 Playwright 스펙으로
바꿨다. §5에는 도출식이 버전을 빼는 것을 무엇이 고정하는지 적었다.

renamed의 필드 추가에는 §4의 리더-우선 순서가 적용되지 않는다는 것도
명시했다. 그 순서는 relay에 저장돼 구버전이 읽는 provenance의 steps 어휘를
위한 것이고, Ledger는 apply command의 반환값으로만 살아 생산자와 소비자가
같은 빌드 안에 있다. steps 쪽 규칙은 그대로다.

E1 계획서에 세 번째 계획 이탈을 보탰다 — Task 5의 featureGate는 구현 중에
뒤집혔고(매니페스트에 없는 ID는 fail-open이라 무음 no-op) 실제 게이트는
SettingsView의 역할 검사다. 앞서 둘만 적어 목록이 불완전했다.

BASELINE에는 게이트 18줄과 재주입 결과표를 남겼다. 셋 중 둘만 단독
방어선이었고, upgrade 테스트의 캔버스 단언은 아니었다 — no_change가 캔버스
단계 앞에서 반환하기 때문이다. 그 사실과, 새 Tauri command를 더하는 세션은
mock 핸들러도 함께 더해야 한다는 것을 기록했다.

Phase 3 밖에 남은 것 넷은 그대로다: generation 증가 경로, 선점의 약한
형태와 위임 실행 요청, CLI 적용 경로, 나머지 8개 업무방 콘텐츠.

Signed-off-by: Dev_YongT <devyongt@gmail.com>
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.

1 participant