Commit Graph

1138 Commits

Author SHA1 Message Date
claude code agent 227
e2d180ab0b test(ai-chat): cover crypto/SSRF/assistant-parts; a11y + refactors
Closes the ai-chat code-review follow-ups.

Tests (security-critical paths previously uncovered):
- secret-box.spec: AES-256-GCM round-trip, random-salt uniqueness, tampered
  blob / wrong APP_SECRET throw the expected message.
- ssrf-guard.spec: isIpAllowed blocks loopback/link-local/private/CGNAT/ULA/
  unspecified/IPv4-mapped, allows public; isUrlAllowed blocks bad scheme,
  invalid URL, IP literals, and DNS-rebinding (mocked dns.lookup).
- ai-chat.service.spec: assistantParts emits output-error for an UNPAIRED
  tool call (guards the MissingToolResultsError fix), output-available when
  paired, skips malformed calls; serializeSteps/rowToUiMessage.
- ai-chat-tools.service.spec: JSON-string node/content coercion + invalid-JSON
  throws; updatePageJson title-only vs object.
- page-embedding.repo.spec: empty spaceIds early-returns [] with no DB call.

a11y:
- Chat history toggle and conversation rows are now keyboard-operable
  (role=button, tabIndex, Enter/Space), matching history-item.tsx.

Refactors:
- onError on useChat adopts the server chat id when the first turn errors
  (AI SDK v6 onFinish doesn't fire on error).
- isToolPart exported once from tool-parts and shared (was duplicated).
- buildInitialValues() dedups the ai-mcp-server-form initial values.
- describeProviderError replaces two inline statusCode/message snippets.
- tool-parts stale tool-list comment refreshed.

Implements docs/backlog/ai-chat-review-followups.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 05:51:52 +03:00
claude_code
c8af637654 Merge branch 'develop' of https://gitea.vvzvlad.xyz/vvzvlad/gitmost into develop 2026-06-20 04:19:24 +03:00
claude_code
ddb93525d6 docs: replace CLAUDE.md with AGENTS.md, codify agent workflow (#2)
## What

Renames the agent-guidance file `CLAUDE.md` → `AGENTS.md` (the opencode-standard name) and prepends a process layer on top of the existing technical/architecture content.

## Changes

- **Rename** `CLAUDE.md` → `AGENTS.md` (git detects 64% similarity, history preserved).
- **New top section "Жизненный цикл задачи"** codifies the workflow we just went through:
  1. Sync with `develop`, branch off
  2. Implement (per the system-prompt workflow + subagents)
  3. **Commit ONLY to Gitea and ONLY as `claude_code`** — never as `vvzvlad`, never push to `origin`/`upstream`
  4. Push + PR to `develop`
  5. User merges; agent deletes the task's `docs/backlog/<task>.md`
- **New release-cycle section**: before cutting a version, run the three orchestrator skills (test-orchestrator, review-orchestrator, red-team-orchestrator), fix their findings, then tag per the existing procedure.
- **Credentials cheat-sheet**: agent identity, keychain service name (`gitea-claude-code`), Gitea PR API endpoint, base branch, and do-not-push warnings for `origin`/`upstream`.
- **Fix typo**: repo slug is `gitmost`, not `gtimost` (the remote was redirecting on every push). Local `gitea` remote URL is updated to the canonical form.

## Out of scope

No code changes — docs only.

Reviewed-on: #2
Co-authored-by: claude_code <claude_code@vvzvlad.xyz>
Co-committed-by: claude_code <claude_code@vvzvlad.xyz>
2026-06-20 04:19:03 +03:00
vvzvlad
9fcec4d295 docs: remove backlog doc for broken import formats
Delete the backlog documentation that described the removal of non‑functional DOCX, PDF, and Confluence import features now that the code changes have been merged.
2026-06-20 04:11:19 +03:00
efc36890ec Merge pull request 'refactor(import): remove non-functional DOCX/PDF/Confluence import stubs' (#1) from remove-broken-import-formats-backend into develop
Reviewed-on: #1
2026-06-20 04:07:40 +03:00
claude_code
732aaf54f8 refactor(import): remove non-functional DOCX/PDF/Confluence import stubs
These import paths relied on the private EE module that was deleted from
the repo. In the community build they either threw 'enterprise license'
(DOCX/PDF) or silently no-op'd (Confluence). The frontend buttons were
already removed in 38064064; this cleans up the dead backend stubs.

- import.service.ts: drop processDocx/processPdf methods, their dispatcher
  branches, the pageId computation + insertPage spread, and the now-unused
  moduleRef param/ModuleRef import
- file-import-task.service.ts: drop the Confluence branch and the now-unused
  moduleRef param/ModuleRef import
- import.controller.ts: restrict file extensions to .md/.html and zip
  sources to generic/notion; update the error message accordingly
- file.utils.ts: remove Confluence from the FileImportSource enum
- features.ts: remove the unused CONFLUENCE_IMPORT/DOCX_IMPORT/PDF_IMPORT
  feature keys

The isConfluenceImport logic in import-attachment.service.ts is intentionally
left in place (real shared attachment-parsing code, not a stub); its removal
is a separate, riskier refactor.
2026-06-20 04:05:29 +03:00
vvzvlad
053a9c0d3f docs(public-share): add model & voice input notes to public share plan
docs: add AI agent roles plan documentation
2026-06-19 16:25:21 +03:00
vvzvlad
3d03417c73 fix(import): surface real error cause in /pages/import instead of generic 400
The two catch blocks in importPage() threw an opaque "Error processing file
content" / "Failed to create imported page" BadRequest, hiding the real cause
from the HTTP response. This made a production 400 regression impossible to
diagnose without server log access, and violated the project convention that
errors must never be swallowed.

Extract `${err.name}: ${err.message}` into both the log (full err object kept
for the stack) and the thrown BadRequestException. Inner processMarkdown/
processHTML rethrowing catches and the EE processDocx/processPdf license
catches are left unchanged.

Local reproduction of the happy-dom 14->20 theory failed (full import chain
+ 22 edge cases pass on happy-dom@20.8.9), so the root cause is still pending
the now-visible reason from a recurring 400. Diagnostic script test-import.tsx
added; backlog doc updated with findings.
2026-06-19 16:25:12 +03:00
vvzvlad
d38de4943d docs: update macOS app repo link to gitmost-app
Rename the macOS app repository reference in README.md and README.ru.md
from vvzvlad/docmost-app to vvzvlad/gitmost-app (both the URL and the
link label), reflecting the renamed repo.
2026-06-19 16:24:47 +03:00
vvzvlad
11d789cdea feat(comment): don't auto-open comments panel when adding new comment
- Remove automatic panel opening in handleAddComment
- Remove automatic panel opening in handleAddReadOnlyComment
- Keep panel open on click for existing comments in editor
2026-06-19 16:10:23 +03:00
vvzvlad
b1d48d9d9a fix(client): compact page tree + fix selection highlight alignment
Reduce DocTree row stride from 32px to 26px for a denser sidebar tree,
and fix the selection/hover highlight that looked unbalanced at the
tighter spacing.

Root causes:
- The virtualized <li> had no explicit height, so `.node`'s height:100%
  collapsed to content height; combined with the asymmetric
  `[role="treeitem"] { padding-bottom: 2px }` rule, row content was
  pushed to the top of the highlight pill (icon glued to the top edge).
- NodeMenu / CreateNode action icons used the default Mantine ActionIcon
  size (md = 28px), overflowing the tighter 26px row stride onto
  neighbouring rows.

Changes:
- doc-tree.tsx: rowHeight 32 -> 26; give each row <li> a definite
  height = rowHeight.
- tree.module.css: rowWrapper fills the slot (height:100%); node pill is
  inset and vertically centered (height: calc(100% - 4px)); drop the
  asymmetric [role="treeitem"] padding-bottom.
- space-tree-node-menu.tsx / space-tree-row.tsx: action icons size={20}.
- share.module.css: drop now-dead .treeNode padding-bottom override.

Verified in an isolated browser harness: highlight content is centered
(2.8/2.8px) and nothing overflows the row stride.
2026-06-18 23:08:42 +03:00
vvzvlad
53b7314705 docs(mcp): detail iOS AGPL licensing blocker in plan
Add a dedicated section describing the licensing conflict between the AGPL‑3.0‑licensed web client and App Store DRM/usage rules. Explain why this is a non‑technical blocker, outline possible distribution approaches (server‑loaded client, OTA updates, PWA, sideload), and recommend confirming the chosen path before implementing any iOS wrapper code.
2026-06-18 22:54:23 +03:00
vvzvlad
20867b0689 fix(client): tighten home page list vertical density
Reduce the recent/favorites/created-by-me list tables from
verticalSpacing="sm" (12px Td padding) to a numeric 6px, removing
~12px of extra height per row so the home page lists pack closer
together. The shared RecentChanges table also drives the space home
view, so both stay consistent.
2026-06-18 22:47:05 +03:00
vvzvlad
0457ba817a feat(client): remove Overview item from space sidebar
Drop the Overview home link that sat between the space switcher and
the Pages section in the authenticated space sidebar. Remove the JSX
block and clean up the now-unused imports (UnstyledButton, IconHome,
useLocation, getSpaceUrl) and the local `location` variable.
2026-06-18 22:41:45 +03:00
vvzvlad
7a033b6d6c docs(mcp): document user‑specific auth and full tree toggle
Add markdown files describing the per‑user authentication mechanism and the ability to expand or collapse all nodes in the page tree, improving guidance for developers working with the MCP backlog feature.
2026-06-18 22:38:09 +03:00
vvzvlad
66bb0813a8 fix(client): tighten page tree vertical density
Reduce DocTree default rowHeight from 32px to 26px so sidebar page
tree rows pack closer together. The virtualizer uses rowHeight as the
row stride (estimateSize + translateY), and row content is only ~22px
tall, so the previous 32px left an ~8px gap between nodes. Both the
space tree and the shared (public) tree inherit the default, so both
become denser and stay consistent.
2026-06-18 20:46:23 +03:00
vvzvlad
a0a7d62b59 feat(client): replace space switcher popover with always-visible space grid
Rework the space sidebar:
- remove the "New page" and "Search" menu items (search stays in the app
  header; page creation stays via the "+" button in the Pages section)
- move "Space settings" into a gear icon next to the current space name
- drop the searchable space popover and render all spaces as an
  always-visible grid of fixed-width cards (icon + name), several per row,
  sorted alphabetically, with the active space highlighted
- always inject the active space into the grid so it stays highlightable
  even when the user has more than the 100-space API page limit

The shared SpaceSelect component is left untouched (still used by the
move/copy page modals).
2026-06-18 20:43:07 +03:00
vvzvlad
e7b7f48d35 docs(backlog): add AI chat collapse and comment density docs
Add two new backlog documentation files:
- ai-chat-collapse-on-page-focus.md describing auto‑collapse behavior for the AI chat window.
- comments-panel-density.md outlining UI density improvements for the comments panel.
2026-06-18 20:41:17 +03:00
vvzvlad
850d9ada22 docs(backlog): design server-authoritative realtime tree updates
Add a backlog design note for making page-tree realtime updates
server-authoritative instead of client-relayed.

Problem: page content syncs via Yjs/Hocuspocus (server-authoritative),
but tree create/move/delete is broadcast by the originating browser only,
so non-UI creation paths (AI agent, MCP, REST API, import) and lost-event
races leave other clients' sidebars stale.

The note specifies a WsService.emitTreeEvent broadcaster, WsTreeService
broadcast helpers, a PageWsListener on PAGE_CREATED/SOFT_DELETED/DELETED/
MOVED/RESTORED, event-payload enrichment to avoid the in-transaction
re-fetch race, a dedicated PAGE_MOVED event, removal of the client relay,
plus edge cases, work breakdown, tests, alternatives and open questions.
2026-06-18 20:34:18 +03:00
vvzvlad
1e7a306f96 feat(mcp): add hierarchical tree mode to list_pages
list_pages gains an opt-in `tree` parameter on both surfaces (the
@docmost/mcp server tool and the AI-chat agent tool), which share the
same DocmostClient.listPages. Default behavior (recent-by-updatedAt flat
list) is unchanged.

- client.ts: listPages(spaceId?, limit=50, tree=false); when tree is
  true it requires spaceId (throws a specific error otherwise), walks the
  sidebar tree via the existing bounded/cycle-safe enumerateSpacePages,
  and returns a nested tree; limit is ignored in tree mode.
- lib/tree.ts: new pure buildPageTree() — lean nodes { id, slugId, title,
  children? }, children sorted by position (code-unit order), orphans
  promoted to roots, cycle-safe.
- index.ts + ai-chat-tools.service.ts: expose `tree` in the tool schemas
  and descriptions; docmost-client.loader.ts: mirror the new signature.
- tests: add packages/mcp/test/unit/tree.test.mjs (nesting, ordering,
  lean shape, orphan promotion, cycle/self-reference safety).
- rebuild @docmost/mcp (build/ is tracked and loaded at runtime).
2026-06-18 20:30:00 +03:00
vvzvlad
8178d21c00 fix(client): glue version string to logo on the app header baseline
Wrap the logo link and the APP_VERSION text into a single bottom-aligned
Group so they read as one lockup ("gitmost v0.9..."). Move the version
styling into a new .brandVersion CSS class: shrink it from 12px to 10px,
keep the dimmed color and selectability, and lift it via margin-bottom so
its text baseline sits on the wordmark baseline of the 30px desktop logo
(derived from the logo SVG geometry). Drop the redundant lh prop.
2026-06-18 20:13:46 +03:00
vvzvlad
5d8860e47b docs(backlog): clean up backlog documentation
Remove outdated process sections from several backlog markdown files and add new backlog items for AI chat step limits, endpoint status config, and API key field UI improvements.
2026-06-18 20:02:01 +03:00
vvzvlad
9b8ac430b2 fix(ai-chat): preserve scroll position during agent message streaming
The transcript force-scrolled to the bottom on every streamed delta because
the auto-scroll effect ran unconditionally whenever the messages array identity
changed. Scrolling up to read earlier messages was impossible — each token
yanked the view back down.

Implement a "stick to bottom" pattern in MessageList:
- track whether the viewport is pinned to the bottom via a scroll listener
  (pinnedToBottomRef, BOTTOM_THRESHOLD = 40px);
- only auto-scroll while pinned; a freshly sent user message always re-pins;
- attach the scroll listener via a [hasScrollArea] dependency so a brand-new
  empty chat (whose ScrollArea mounts only after the first message) wires it up;
- guard the effect's own scrollTop write (programmaticScrollRef) so it is not
  misread as a user scroll.
2026-06-18 19:54:40 +03:00
vvzvlad
f96df1c540 feat(ai-chat): show current context size instead of total tokens spent
The floating AI-chat header badge summed metadata.usage (AI SDK
totalUsage, all steps) across every assistant row, showing the
cumulative tokens SPENT — which grows each turn as history is re-sent.
Replace it with the conversation's CURRENT context size.

- server: persist metadata.contextTokens in streamText onFinish from the
  final-step `usage` (inputTokens + outputTokens ≈ current context
  window occupancy); keep usage: totalUsage for back-compat/fallback
- client: derive the badge from the most recent assistant row's
  contextTokens (fallback to that row's usage total for older chats)
  instead of summing all rows
- types: add metadata.contextTokens to IAiChatMessageRow
- i18n: rename badge label "Tokens used in this chat" -> "Current
  context size" (en-US)

No DB migration needed (metadata is a JSON column).
2026-06-18 19:54:34 +03:00
vvzvlad
411671bad2 docs(backlog): STT extra providers + async transcription roadmap
Add docs/backlog/stt-providers-and-async.md: how to add new synchronous STT
request formats (Deepgram, native Gemini, ElevenLabs) via the explicit
sttApiStyle axis, which providers are inherently async and don't fit the
current sync model, and a target job-based async architecture (BullMQ job
table, sync+async unification, polling -> push -> live streaming) with the
migration path and security/cleanup considerations.

Add docs/streaming-dictation-plan.md — a design document for true
"text appears as you speak" dictation via the OpenAI Realtime API.

- Maps the current batch dictation flow (client MediaRecorder -> single
  blob -> POST /ai-chat/transcribe) and why streaming is impossible there.
- Documents the Realtime API contract (transcription session, ephemeral
  token, pcm16 audio, input_audio_buffer.append, input_audio_transcription
  delta/completed events, server_vad).
- Recommends a server-side WS proxy transport (key stays server-side,
  SSRF-guarded, provider-agnostic via sttBaseUrl) over direct browser
  WebRTC, and a ProseMirror decoration for interim text with final-only
  commit to avoid polluting Yjs collab/history.
- Covers config additions, AudioWorklet PCM16 capture, security per repo
  conventions, edge cases, phased rollout, risks, and impacted files.
2026-06-18 19:44:16 +03:00
vvzvlad
01a5a4b5d2 refactor(ai): explicit STT request format instead of OpenRouter host-sniffing
Replace the implicit `hostname endsWith openrouter.ai` detection with an
explicit, admin-chosen provider field `sttApiStyle` ('multipart' = OpenAI-
compatible multipart /audio/transcriptions; 'json' = OpenRouter-style JSON +
base64 input_audio). The transcription path now branches on the stored field,
not on the URL — nothing hidden from the admin.

- ai.types: add SttApiStyle + STT_API_STYLES; field on AiProviderSettings and
  MaskedAiSettings (resolved via ResolvedAiConfig).
- update-ai-settings.dto: validate sttApiStyle with @IsIn(STT_API_STYLES).
- ai-settings.service: plumb sttApiStyle through resolve()/getMasked() and the
  non-secret update whitelist; workspace.repo: add it to the ALLOWED array so it
  persists.
- ai.service: drop isOpenRouter(); transcribe() branches on cfg.sttApiStyle;
  rename helper to transcribeJsonBase64 with provider-neutral error text and a
  BadRequestException (400) when the base URL is missing for the JSON style.
- client: SttApiStyle type on IAiSettings/IAiSettingsUpdate; "Request format"
  Select on the Voice/STT settings card; i18n.
2026-06-18 19:40:05 +03:00
vvzvlad
77249d59c6 feat(ai): OpenRouter STT support + real error surfacing + STT endpoint test
- ai.service: route *.openrouter.ai STT to its JSON+base64
  /audio/transcriptions API; keep the OpenAI multipart path (AI SDK) for
  OpenAI/self-hosted whisper. Unify transcription behind transcribe().
- /transcribe controller: surface the real provider/transport reason
  (describeProviderError) instead of an opaque 500; preserve HttpException.
- testConnection: add an 'stt' capability (silent-WAV probe) + DTO; client
  gets a Test endpoint button and status dot on the Voice/STT card.
- useDictation: log full errors to the console and show the real reason
  (mic start + transcription paths); handle NotReadable/Abort and missing
  mediaDevices.
- docs(CLAUDE.md): require full error logging + specific user-facing messages.
2026-06-18 19:26:35 +03:00
vvzvlad
ef90655657 docs: add error handling guidelines to docs
Add a documentation bullet that enforces comprehensive error logging and user‑facing messages, ensuring caught errors are fully logged and presented with specific, human‑readable explanations rather than generic messages.
2026-06-18 19:10:03 +03:00
vvzvlad
16dc7ea5cf docs(readme): add voice dictation to README and delete its plan
The README files now list Voice dictation as a completed feature () instead of an upcoming one (🔭). Consequently, the detailed `voice-dictation-plan.md` documentation has been removed. This reflects that the feature is ready and no longer merely a plan.
2026-06-18 19:00:01 +03:00
vvzvlad
beb95cbbbb docs: add migration ordering guidance
Add a section describing how Kysely runs migrations in alphabetical (timestamp) order and the need to verify migration timestamps when merging branches. This helps prevent migration ordering errors and boot failures.
2026-06-18 18:56:39 +03:00
vvzvlad
5af40e0ee5 refactor(db): replace STT credentials migration
Remove the outdated 20260618T130000 migration file and add the updated
20260618T160000 version to correct the timestamp and ensure proper ordering.
2026-06-18 18:54:24 +03:00
vvzvlad
9dd5bf680d docs(readme): update roadmap with new feature plans
Update the README files to list newly planned features on the roadmap, including page templates, a public‑share AI assistant, and academic‑style footnotes. This improves documentation of upcoming functionality.
2026-06-18 18:48:31 +03:00
vvzvlad
9766c0fee6 docs: add public share assistant plan
Add a detailed design and implementation plan for an AI assistant that
operates on publicly shared document trees. The document outlines the
feature scope, architecture, security considerations, and remaining work,
providing context for future development.
2026-06-18 18:47:56 +03:00
vvzvlad
6bbddc636f docs: add footnotes feature plan and list it on the roadmap
Add docs/footnotes-plan.md describing the footnotes feature design:
reference + definitions model (Markdown/pandoc-style) chosen over the
self-contained inline-atom approach for Yjs/Hocuspocus collaboration
safety. Covers the three nodes (footnoteReference / footnotesList /
footnoteDefinition), decoration-based numbering, hover-popover reading,
HTML/Markdown round-trip, MCP schema mirror + commentsToFootnotes
migration, edge cases, affected files, phased plan and test strategy.
2026-06-18 18:46:33 +03:00
vvzvlad
a473df7f32 docs: add design plan for live page-template embeds
Add docs/page-templates-plan.md describing a whole-page live
transclusion feature: pages flagged is_template, a new pageEmbed
node referencing a source page, a whole-page lookup endpoint reusing
the existing transclusion access-control and share paths, reference
sync, duplicate remap, and cycle/deletion/access/export edge cases.
Decision: separate pageEmbed node over extending transclusionReference.
2026-06-18 18:45:57 +03:00
vvzvlad
874bdd021c feat(ai): server-side voice dictation (STT) with mic in chat and editor
Add push-to-talk voice dictation that transcribes recorded audio on the
server via the workspace's OpenAI-compatible AI provider (Whisper /
gpt-4o-transcribe / self-hosted whisper), then inserts the text.

Backend:
- New `stt_api_key_enc` column + migration; STT creds parity with chat/
  embeddings (sttModel/sttBaseUrl/sttApiKey, write-only key, fallbacks to
  chat baseUrl/key). Both provider whitelists updated (service + repo).
- AiService.getTranscriptionModel + AiTranscriptionService.
- Gated POST /ai-chat/transcribe (dictation flag → 403, JWT + workspace
  scope + throttle, 25MB cap, MIME whitelist, never logs audio/key).
- New `settings.ai.dictation` workspace flag (DTO + service + audit).

Frontend:
- Wire up the Voice/STT settings card (model/base URL/key) and the
  Voice-dictation toggle.
- New `features/dictation`: useDictation (MediaRecorder state machine),
  MicButton, transcribe service; integrated into the chat composer and a
  new editor-toolbar dictation group, both gated by ai.dictation.
2026-06-18 18:45:33 +03:00
vvzvlad
49eba22201 docs(claude): document the release procedure
Add a "Cutting a release" subsection to CI / release: version selection
(SemVer), synchronized package.json bump (root + client + server; mcp is
independent), CHANGELOG update, lightweight v-tag, and push that triggers
release.yml. Document moving a misplaced tag via git tag -f + force-push,
and note the git tag (not package.json) is the source of truth for the
displayed version.
2026-06-18 18:09:15 +03:00
vvzvlad
c6b878c514 0.91.0
Bump root, client and server package versions 0.90.1 -> 0.91.0 to match the
v0.91.0 release tag. packages/mcp keeps its independent 1.0.0 version.
2026-06-18 18:07:54 +03:00
vvzvlad
0ceea15e87 feat(ai-chat): wider default window, larger max size, smaller font
Make the floating AI chat window open at a larger default size and
allow stretching it further, plus shrink the fonts.

- ai-chat-window.tsx: DEFAULT_WIDTH 362->540, DEFAULT_HEIGHT 602->680;
  clamp the default width to the viewport in computeInitialGeom()
  (symmetric with the existing height clamp) to avoid overflow on
  narrow screens.
- ai-chat-window.module.css: raise resize caps (max-width 560->900px,
  max-height 880->1100px); base font-size 12->11px.
- ai-chat.module.css: chat content font .messages sm->xs.
2026-06-18 18:06:48 +03:00
vvzvlad
9039a50da9 docs: add CLAUDE.md for AI agents
Add a root CLAUDE.md describing the Gitmost project for Claude Code and
other AI-agent tooling. Covers: the AGPL-only/no-EE fork philosophy and the
"internal identifiers stay docmost" naming gotcha; the pnpm+Nx monorepo
layout; build/lint/test (incl. single-test) and migration commands; and the
big-picture architecture — the two server processes (API on PORT, collab on
COLLAB_PORT via Hocuspocus/Yjs), NestJS module layout, Kysely/Postgres+
pgvector/Redis persistence, the shared editor-ext (client + server; the MCP
package vendors its own schema mirror), and the two AI subsystems (embedded
/mcp server and the CASL-scoped AI chat agent with RAG).
2026-06-18 18:05:48 +03:00
vvzvlad
84f99e3ec1 chore(gitignore): add .claude/worktrees/ to ignore list 2026-06-18 17:53:40 +03:00
vvzvlad
06648d91bb feat(ai-chat): copy agent chat as Markdown to clipboard
Add a header button to the AI agent chat window that copies the active
conversation to the clipboard as Markdown, including the request
internals already persisted client-side — tool calls with their
input/output, per-message token usage, and finish/error info. No new
network call and no server/DB change: it serializes the already-loaded
persisted message rows.

- New util chat-markdown.ts (renamed from export-chat.ts): pure
  buildChatMarkdown() serializer reusing the tool-parts helpers so tool
  labels match the on-screen labels; fence() escapes embedded code
  fences.
- ai-chat-window.tsx: Copy button (shown only for a saved chat with
  loaded rows) using the project useClipboard hook; toggles a check
  icon on success and shows the standard "Copied" notification. Drag is
  unaffected (startDrag ignores button clicks).
- en-US: add "Copy chat" key, drop the obsolete "Export chat".
2026-06-18 17:53:18 +03:00
vvzvlad
8f5be58f9b feat(ai-chat): move AI chat icon into the global top bar
Move the AI-chat toggle icon (IconSparkles) from the page header menu
into the global top bar, placed next to the notifications icon. The
"AI chat enabled" gate (workspace.settings.ai.chat) is preserved, and
the icon style is aligned with the neighbouring notifications icon
(subtle, size sm). As a result the entry point is now available on all
routes instead of only on page routes.

- app-header.tsx: render the gated AI-chat ActionIcon before
  NotificationPopover; wire it to aiChatWindowOpenAtom.
- page-header-menu.tsx: remove the old AI icon block and its now-unused
  imports/locals.
2026-06-18 17:50:42 +03:00
vvzvlad
3d9c9daf98 feat(ai-chat): focus composer on chat creation
Add autoFocus to the chat composer Textarea so a freshly created chat
(window open, "New chat", chat switch — all remount ChatThread via key)
lands with the cursor ready in the input field, letting the user type
immediately without clicking into it.
2026-06-18 05:51:24 +03:00
vvzvlad
cee9f6fb6e docs(backlog): add AI chat backlog documentation 2026-06-18 05:50:10 +03:00
vvzvlad
a945b47749 fix(mcp): verifiable mutation results + refuse formatting edits in edit_page_text
edit_page_text reported "success" when asked to change formatting (e.g. remove
strikethrough): the markdown-strip fallback matched the bare text, the replace
preserved marks, and the tool returned success — so the agent believed it had
fixed something that never changed.

Two fixes, both in the shared @docmost/mcp DocmostClient so they reach BOTH the
standalone MCP server and the in-app AI chat (which loads @docmost/mcp):

- Verifiable result for every content mutator: mutatePageContent now computes a
  `verify` change-report (text inserted/deleted, blocks changed, per-mark-type
  delta, integrity/structure delta) via summarizeChange() and returns it on all
  mutators (incl. replaceImage via mutateLiveContentUnlocked). diffDocs is
  text-only, so the mark/structure delta is what surfaces formatting changes.
- edit_page_text hard-refuses formatting edits: applyTextEdits rejects an edit
  whose find/replace differ only in markdown markers (via stripBalancedWrappers,
  which strips balanced wrappers/links without trimming whitespace/emoji, so
  plain-text edits like trailing-space trims, snake_case, math are NOT refused).
  A fully-refused batch errors instead of silently succeeding.

Also updated the model-facing edit_page_text descriptions in BOTH tool layers
(packages/mcp/src/index.ts and ai-chat-tools.service.ts) to drop the misleading
"strip-and-retry tolerated" wording and point formatting changes to patch_node.

New unit tests: test/unit/diff-verify.test.mjs, test/unit/json-edit-refuse.test.mjs.
2026-06-18 05:46:13 +03:00
vvzvlad
ca0622ef01 feat(client): persist page-tree open/closed state per workspace+user
The sidebar page tree previously kept its expanded/collapsed state in an
in-memory jotai atom, so it reset on every reload. Persist it in
localStorage instead, scoped by `${workspaceId}:${userId}` so multiple
accounts sharing a browser origin don't leak tree state into each other.

- open-tree-nodes-atom: replace the in-memory atom with a facade over an
  atomFamily of atomWithStorage, scoped via currentUserAtom; keep the same
  [OpenMap, functional-updater setter] public API so space-tree needs no
  change.
- Use an explicit synchronous createJSONStorage + getOnInit: true so the
  saved state is read at atom init — no collapse-then-expand flicker on
  reload and no write against an un-hydrated empty map.
- README / README.ru: document persistent page-tree state in the
  "What's different from Docmost" table.
2026-06-18 05:42:12 +03:00
vvzvlad
1968879fe5 feat(ai-chat): add Markdown export button for agent chat
Add an "Export chat" button to the AI agent chat window header that
downloads the active conversation as a Markdown file. The export is
client-only: it serializes the already-loaded persisted message rows
(no new network call, no server/DB change) and includes the request
internals the chat already holds — tool calls with their input/output,
per-message token usage, finish reason and error info.

- New util apps/client/src/features/ai-chat/utils/export-chat.ts:
  buildChatMarkdown() + exportChatAsMarkdown(); reuses tool-parts
  helpers so tool labels match the on-screen labels; fence() escapes
  embedded code fences; slugify() yields a safe filename with a chatId
  fallback; downloads via file-saver's saveAs.
- ai-chat-window.tsx: IconFileExport button in the header, shown only
  for a saved chat with loaded rows (canExport); drag is unaffected.
- en-US: add "Export chat" and "You" i18n keys.
2026-06-18 05:12:15 +03:00
vvzvlad
4d25c6fe2e docs(backlog): add backlog doc for AI chat current page fragile 2026-06-18 04:53:01 +03:00
vvzvlad
a0a8d3c97f feat(client): show git-describe version next to the brand logo
Display the app version (output of `git describe --tags`) in the header
beside the gitmost logo: a clean tag renders as `vX.Y.Z`, otherwise the
tag plus commits-since and short hash (e.g. v0.90.1-56-g25975acd).

- vite.config.ts: resolve APP_VERSION from env (Docker/CI) -> git describe
  (local) -> package.json version fallback
- app-header.tsx: render APP_VERSION after the brand block (ml="md"),
  nudge the Home nav group (ml={50} -> "xl")
- Dockerfile: accept APP_VERSION build-arg in the builder stage (.git is
  excluded from the build context)
- CI: pass APP_VERSION build-arg — release.yml uses the tag, develop.yml
  computes git describe with fetch-depth: 0
- nx.json: add APP_VERSION to the build target inputs so the cache
  invalidates when the version changes
2026-06-18 04:51:21 +03:00