Commit Graph

1495 Commits

Author SHA1 Message Date
claude_code
74e2b7ad7f Merge branch 'fix/ai-chat-role-cards-fit' into develop
Fit full role-card description text in the AI chat empty state and show a
generic "AI is typing…" indicator (role name kept only as the dimmed
interlocutor label).
2026-06-21 17:11:56 +03:00
claude_code
a86d0c7c3b fix(ai-chat): always show generic "AI is typing…" indicator
The typing indicator rendered "<role name> is typing…". Show a generic
"AI is typing…" instead and keep the role/identity name only in the
dimmed interlocutor label above the typing dots.

- typing line now always renders t("AI is typing…")
- add the "AI is typing…" key to en-US and ru-RU locales
- sync stale doc comments that referenced the old text

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 17:11:21 +03:00
claude_code
569da822b6 fix(ai-chat): fit full role-card description text in new-chat empty state
The colored role cards in the AI chat empty state truncated their
admin-configured description with an ellipsis and could clip the top row
when the cards overflowed. Make the full text fit:

- drop the description lineClamp so the whole text renders
- add overflow-wrap: anywhere so long unbreakable tokens (URLs) wrap
- switch the cards container to align-content: flex-start so an
  overflowing top row stays reachable while scrolling (the parent
  Mantine Center still vertically centers the block when it fits)
- widen the card max-width 180px -> 200px for more text room

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 17:11:21 +03:00
claude_code
f8e8ada581 test(server): add behavioural unit tests for auth + common security helpers
Batch 1 of the test-strategy rollout. Fills the highest-value gaps where
existing specs were only `toBeDefined()` smoke tests or absent. Test-only,
no production source touched.

- token.service.behavior.spec.ts: verifyJwt type-mismatch rejection (confused
  deputy), generateAccessToken/generateCollabToken disabled-user -> Forbidden,
  agent `actor` claim only from signed provenance, correct expiry.
- auth.util.spec.ts: computeEmailSignature (stable HMAC, case-normalized),
  throwIfEmailNotVerified, validateSsoEnforcement, validateAllowedEmail;
  it.todo flags the unguarded `@`-less email TypeError.
- guards/setup.guard.spec.ts: cloud blocks setup, first-run allows, re-run on
  an initialised instance is forbidden (privilege escalation guard).
- security-headers.spec.ts: resolveFrameHeader clickjacking/CSP branches.
- utils.security.spec.ts: redactSensitiveUrl, extractBearerTokenFromHeader,
  parseRedisUrl, normalizePostgresUrl, diffAuditTrackedFields, isUserDisabled.

60 tests + 1 todo, all green. Reviewed for mutation resistance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 17:00:09 +03:00
claude_code
4720705155 Merge branch 'docs/review-followups' into develop
Review follow-ups (docs only):
- CHANGELOG [Unreleased]: post-0.93.0 share-AI cap lowered 300->100 (#62)
- backlog: track deferred AiChatService.stream integration coverage
2026-06-21 16:57:13 +03:00
claude_code
ce60498a90 docs: track post-0.93.0 share-AI cap change + deferred stream-coverage debt
Follow-ups from the multi-aspect review of the e5bc82c7..d4658d4c range.

- CHANGELOG: document under [Unreleased] that the default per-workspace
  hourly public-share assistant cap was lowered 300 -> 100 after the
  v0.93.0 tag (#62). v0.93.0 shipped 300, so existing deployments that
  never set SHARE_AI_WORKSPACE_MAX_PER_HOUR drop to 100 on upgrade.
- Recreate the still-open Section 3 (AiChatService.stream integration
  coverage) of the deleted feature-test-coverage-deferred.md as a focused
  backlog doc so the test debt stays tracked; Sections 1-2 are already
  closed by the integration harness (PR #115).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 16:54:56 +03:00
4a22cc1955 Merge pull request 'feat(ai-chat): role cards start the chat and show role identity' (#121) from feat/ai-chat-role-cards-ux into develop
Reviewed-on: #121
2026-06-21 16:28:51 +03:00
claude_code
b83a5d4597 feat(ai-chat): role cards start the chat and show role identity
Rework the new-chat role-card empty state:
- Remove the "Universal assistant" card; universal assistant is now the
  implicit default the user gets by typing without picking a card.
- Show each role's description on its card (under the emoji and name).
- Clicking a card immediately starts the chat: it binds the role to the
  new chat and sends the default opening prompt "Take a look at the
  current document" (one click, no separate select step). roleIdRef is
  set synchronously before sendMessage so the create request carries the
  role.
- Show the current role's name in the window header badge and as the
  assistant's display name (transcript label + "… is typing…"), falling
  back to "AI agent" for a role-less chat. selectChat resets the picked
  role so it cannot leak into an unrelated existing chat.
- Add the "Take a look at the current document" i18n key (en-US, ru-RU).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 16:20:36 +03:00
claude_code
d4658d4cb3 Merge pull request '#114 refactor(ai-chat): shared parseNodeArg helper; keep duplication backlog doc' (#114) from refactor/ai-chat-tool-spec-registry into develop
# Conflicts:
#	apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts
2026-06-21 14:45:20 +03:00
claude_code
4105836a2d Merge pull request '#112 test(ai-chat): current-page coverage + getCurrentPage helper' (#112) from feat/ai-chat-current-page-robustness into develop 2026-06-21 14:31:12 +03:00
claude_code
f5a45d5453 Merge pull request '#115 test(server): integration harness + deferred coverage' (#115) from test/deferred-integration-coverage into develop 2026-06-21 14:31:12 +03:00
claude_code
9fad6ab73b Merge pull request '#113 feat(ai-chat): role-selection cards empty-state' (#113) from feat/ai-chat-role-cards into develop 2026-06-21 14:31:11 +03:00
claude_code
194924c3ba Merge pull request '#111 feat(ai-chat): collapse-on-page-focus (remove completed backlog doc)' (#111) from feat/ai-chat-collapse-on-page-focus into develop 2026-06-21 14:31:11 +03:00
claude_code
c7f0b51389 fix(ai-chat): keep tool-duplication backlog doc; fix parseNodeArg comment
Pre-merge review follow-up for the parseNodeArg dedupe (PR #114):
- Restore docs/backlog/ai-chat-tool-definitions-duplicated.md instead of
  deleting it: it still tracks open debt (unified spec registry + ProseMirror
  <-> Markdown converter unification) that this branch defers, and
  docs/git-sync-plan.md links to its converter section. Mark the node-arg
  quirk as done and add a Progress section.
- Reword the in-app helper header from "byte-for-byte" to "behaviorally
  identical": the two copies differ in comments/quote style; only the logic,
  throw messages and branch order match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 14:30:37 +03:00
claude_code
ebfe56a684 Merge pull request 'chore: finish the 3 remaining open issues (#93 move-snapshot, #62 cap, #109 ru-RU i18n)' (#117) from chore/finish-open-issues into develop 2026-06-21 14:27:02 +03:00
claude_code
e12ddaa2c8 i18n(ai-chat): complete the ru-RU AI-chat strings + record locale policy (#109)
ru-RU was missing most AI-chat keys, so the chat/typing widgets rendered
mixed-language (some keys fell back to en-US). Fill the full AI-chat string
set in ru-RU and document the maintenance policy.

- ru-RU/translation.json: add the 24 missing AI-chat keys (labels, typing
  indicator, Ask-AI widget, public-share, error messages); keep the typing
  keys grouped; existing translations untouched.
- i18n.ts: add a policy comment near fallbackLng — en-US is the source of
  truth; en-US + ru-RU are fully maintained; the other 10 locales
  intentionally rely on the en-US fallback until contributed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 14:24:18 +03:00
claude_code
6397b500ba fix(share-ai): lower default per-workspace cap to 100 (#62)
The fail-closed limiter behavior (#62 primary item) already shipped; this
finishes the issue by lowering the default hourly per-workspace cap from 300
to 100 to better fit real anonymous-assistant load. Still overridable via
SHARE_AI_WORKSPACE_MAX_PER_HOUR.

- public-share-workspace-limiter.ts: SHARE_AI_WORKSPACE_MAX_PER_WINDOW 300 -> 100.
- .env.example: documented default + example value 300 -> 100.
- public-share-chat.spec.ts: update the default-cap assertion to 100.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 14:24:18 +03:00
claude_code
c3161a05dd refactor(ws): single-snapshot move audience to close the restricted-move race (#93)
Implements Option 2 of #93. The restricted branch of broadcastPageMoved
previously resolved its audience twice — emitToAuthorizedUsers and
emitDeleteToUnauthorized each ran an independent fetchSockets +
getUserIdsWithPageAccess — leaving a race window between the two snapshots
where a socket could receive both the move and the delete (leak) or neither
(lost compensating delete).

- ws.service.ts: add emitMoveWithRestrictionSplit() that takes ONE socket
  snapshot and ONE authorization resolution, then partitions the room:
  authorized users get the moveTreeNode, everyone else (unauthorized +
  anonymous) get the compensating deleteTreeNode. Disjoint + complete by
  construction. Remove the now-unused emitToAuthorizedUsers /
  emitDeleteToUnauthorized; keep private broadcastToAuthorizedUsers (still
  used by emitRestrictedAwareToSpace).
- ws-tree.service.ts: broadcastPageMoved restricted branch now drives move +
  delete from the single method.
- specs: assert the single method is used and that fetchSockets /
  getUserIdsWithPageAccess are each called exactly once (single snapshot);
  re-route ws-service.spec to emitTreeEvent after the method removal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 14:24:18 +03:00
claude_code
77eeada693 Merge develop for the 0.93.0 release v0.93.0 2026-06-21 14:10:00 +03:00
claude_code
06bfca5fdb docs(changelog): 0.93.0 release notes 2026-06-21 14:09:44 +03:00
claude code agent 227
04f05626ad test(server): integration harness + deferred coverage vs real Postgres/Redis
Builds the deferred integration tests from docs/backlog/feature-test-coverage-
deferred.md that needed real infra (a test Postgres + real Redis) which the repo
lacked. Runs against an isolated, auto-created docmost_test database and Redis
logical DB 15 — never the dev data.

Harness (apps/server/test/integration/, run via new `pnpm --filter server test:int`
=> jest --config test/jest-integration.json; default unit `jest` is untouched and
excludes these via the *.int-spec.ts name + rootDir):
- db.ts: buildTestDb() mirrors database.module.ts exactly (PostgresJSDialect,
  CamelCasePlugin, bigint to:20/from:[20,1700] parsing) + minimal seed helpers.
- global-setup.ts: DROP/CREATE docmost_test, CREATE EXTENSION vector, migrate to
  latest via Kysely Migrator (fails loud on any errored migration).
- global-teardown.ts: closes the pool.

Coverage (5 suites, 16 tests, all green against live PG+Redis):
- WorkspaceRepo.updateSetting: jsonb-merge persists htmlEmbed without clobbering
  sibling ai/sharing namespaces (the kill-switch write half).
- AiAgentRoleRepo: soft-delete exclusion, cross-workspace tenant isolation,
  duplicate (name,workspace) -> 23505, name reusable after softDelete (partial
  unique index WHERE deleted_at IS NULL), same name across workspaces allowed.
- page_template_references: deleting either source or referenced page cascades
  the link row (onDelete cascade) — real FK, not mocked.
- PublicShareWorkspaceLimiter vs REAL Redis: real ioredis EVAL of the sliding-
  window Lua — max boundary (3 admit / 4th deny), re-admit after the window
  slides, same-ms distinct members. Catches Lua bugs a FakeRedis cannot.
- AiChatRepo.findByCreator: role-badge join (enabled->badge; soft-deleted or
  disabled role -> null).

Review: APPROVE; applied its two hardening suggestions (fail loud on errored
migration result even without a top-level error; TEST_REDIS_URL override + ping
preflight). tsc clean; unit run excludes int-spec (verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 07:02:55 +03:00
claude code agent 227
f9757fda12 refactor(ai-chat): dedupe node-arg JSON normalization into a shared helper
First, safe step of docs/backlog/ai-chat-tool-definitions-duplicated.md: the
"node may be a JSON object OR a JSON string" quirk was hand-copied at 6 tool
sites. Extract it into a single parseNodeArg() helper per package and call it at
every site. Behavior-preserving — each site's throw message is byte-identical
(patch/insert: 'node was a string but not valid JSON'; update_page_json: 'content
was a string but not valid JSON'); no tool name/description/schema changed.

Two helper copies (packages/mcp/src/lib/parse-node-arg.ts and
apps/server/src/core/ai-chat/tools/parse-node-arg.ts) are intentional: the
ESM-only @docmost/mcp cannot be imported by the CommonJS server (it is loaded at
runtime via the Function('import()') trick), so runtime code cannot cross that
boundary by a normal import. Each copy is now the single source within its
package (6 inline copies -> 2 helpers). packages/mcp/build rebuilt in sync.

Tests: parse-node-arg.spec.ts (server, Jest) + parse-node-arg.test.mjs (mcp,
node:test) — object passthrough, valid-string parse, invalid-string throw with
the right message. Server tsc clean; mcp suite 254 pass; agent structural-edit
path verified live in-browser (agent inserted a node, persisted to the doc).

Deferred (documented for the record, since the backlog doc is removed with this
commit): the FULL transport-agnostic tool-spec registry (one name+schema+
description per tool shared by both transports) and deriving DocmostClientLike
from the real client type. Both are blocked by the current architecture, not by
effort: (1) @docmost/mcp ships no type declarations and is ESM-only, so a
type-only derivation needs declaration emission + tsconfig path wiring, and the
real client's precise return types break the in-app tool test stubs (attempted,
reverted to keep tsc green); (2) the two transports intentionally DIVERGE in tool
NAMES (snake_case x38 vs camelCase x41), membership (in-app adds getCurrentPage/
listSidebarPages, omits delete_comment/image tools) and model-facing
DESCRIPTIONS, so a unified registry would change behavior on BOTH the agent and
external MCP clients and needs its own verification pass. This is forward-looking
debt (the code is correct today), to be done incrementally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:51:09 +03:00
claude code agent 227
19cd73a5aa feat(ai-chat): role-selection cards as new-chat empty-state
Replace the new-chat <Select label="Agent role"> picker with colored role
cards rendered as the empty-state of a brand-new chat (centered in the window),
per docs/backlog/ai-chat-role-cards-empty-state.md. Clicking a card selects that
identity; sending without a pick falls back to the Universal assistant; the
cards disappear once the chat is non-empty. Purely client-side — the existing
selectedAiRoleIdAtom + roleId request wiring (server role fixation on chat
creation) is unchanged.

- new RoleCards rendered through the existing emptyState prop chain
  (AiChatWindow -> ChatThread -> MessageList); MessageList already supported it.
- Universal assistant card (gray, value null, default-selected) + one card per
  enabled role, color cycled from a 10-name Mantine palette via the pure
  roleCardColor() helper; theme-aware CSS vars (light/-light-color/-filled).
- each card is an UnstyledButton with aria-pressed for a11y + testability.
- tests: role-card-color (palette cycling, negative-safe) + role-cards.test.tsx
  (render, emoji/name, selection highlight, click -> onSelect). 9 tests green,
  client tsc clean.

Verified live in-browser: cards (not a Select) show for a new chat; selecting
Пират binds the chat to that role end-to-end (badge + pirate reply); no pick =>
Universal; cards vanish after the first message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:32:16 +03:00
claude code agent 227
e6b1170553 test(ai-chat): cover current-page injection; extract resolveCurrentPageResult
The 'current page' feature (client useMatch openPage + server getCurrentPage
tool + system-prompt injection) was already implemented & merged; this backfills
its missing test coverage and removes the completed backlog doc.

- extract pure resolveCurrentPageResult(openedPage) into current-page.util.ts
  (byte-identical to the prior inline getCurrentPage tool body) so it is
  unit-testable without the dynamically-imported ESM Docmost client; the tool
  now delegates to it.
- current-page.util.spec.ts: 7 cases (null/undefined/no-id/empty-id/full/no-title).
- ai-chat.prompt.spec.ts: +8 cases for the openedPage context line (title+pageId
  present, Untitled fallback for blank/whitespace title, no line when absent/blank
  id, and sandwich ordering before the trailing safety block).

Verified live in-browser: client sends openPage{id,title} on a page and null
off-page; the agent invokes getCurrentPage and answers with the real title+id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:21:38 +03:00
claude code agent 227
2e0f4456e1 docs: remove completed backlog doc for ai-chat collapse-on-page-focus
The feature is already implemented and merged into develop (f6e216cb):
auto-collapse the AI chat window into its header on outside-page pointer,
expand on header click, with keyboard a11y. Verified live in-browser and
covered by collapse-helpers.test.ts (9 tests). Removing the now-completed
planning doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:11:52 +03:00
e5bc82c7f1 Merge pull request 'test: review-batch-2 follow-up coverage (sandbox html-embed, #101 fixes, i18n)' (#110) from test/review-batch-2-followups into develop
Reviewed-on: #110
2026-06-21 05:55:11 +03:00
claude code agent 227
5418e259a6 test(ws): cover the user-provider reconnect-resync branch (#106)
Extract makeConnectHandler(queryClient) (owning the firstConnect flag) from
UserProvider and test it: first connect does NOT invalidate; a reconnect
invalidates both root-sidebar-pages + sidebar-pages. Behavior-identical (#66).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
claude code agent 227
10bff229d6 i18n(ai-chat): add the missing ru-RU typing-indicator keys (#109)
ru-RU had only '{{name}} is typing…' but not 'AI agent' / 'AI agent is typing…',
so the Russian typing indicator was mixed-language. Add them (AI-агент / AI-агент
печатает…) grouped with the named key. en-US is already complete; other locales
intentionally keep the en-US fallback (full translation is a separate effort).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
claude code agent 227
9797751b0a test(html-embed): cover slash-menu gating of the HTML embed item (#98)
Export + test isHtmlEmbedFeatureEnabled: the 'HTML embed' slash item is hidden by
default / when the toggle is off / on broken localStorage (no throw), shown only
when the workspace toggle is exactly true.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
claude code agent 227
ba37907f50 test(editor-ext): cover the html-embed height attr codec (#98, #99)
Extract parse/renderHtmlEmbedHeight and test: '300'->300, absent->null,
'abc'->null (pins the NaN guard), '120px'->120; render 120->data-height, null/0->{}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
claude code agent 227
267bafdd73 test(html-embed): cover sandbox resize/security helpers (#98, #99)
Extract clampHeight + isTrustedHeightMessage + the HTML_EMBED_SANDBOX token
constant from the NodeView and test them: clamp bounds; reject a resize message
from a foreign window / wrong type / NaN/Infinity; accept a valid same-source
finite message; assert the sandbox is exactly 'allow-scripts allow-popups
allow-forms' (no allow-same-origin) and rendered via srcDoc (not src).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
claude code agent 227
b21433af4e test(mcp): round-trip the htmlEmbed passthrough node (#99, #98)
Add htmlEmbed to the schema toYdoc/fromYdoc acceptance cases, asserting source +
height survive, so removing the passthrough node (which prevents 'Unknown node
type: htmlEmbed' on MCP/AI edits of an embed page) fails CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
claude code agent 227
85fd4afa85 test(workspace): cover trackerHead DTO validation, CASL gate, no-op audit (#98)
DTO: trackerHead @IsString/@MaxLength(20000) + htmlEmbed @IsBoolean accept/reject
cases. CASL: a non-admin updating trackerHead/htmlEmbed gets ForbiddenException
(update not called); owner/admin proceed. Audit: a no-op trackerHead re-save
doesn't enter the audit diff.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
claude code agent 227
d9fa804197 test(share): extract + cover injectTrackerHead (#100, #98)
Extract the admin trackerHead <head> injection into a pure injectTrackerHead()
and test it: a snippet containing $&/$$/backtick-dollar survives BYTE-FOR-BYTE
(pins the function-replacer fix), empty/whitespace/undefined and a missing </head>
leave the html unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
claude code agent 227
e8775c45b0 test(ai-chat): cover the conditional assistant-name signature (#108)
Extract the shared assistant-name predicate (resolveAssistantName: trimmed name
or null) used by typing-indicator + message-item, and unit-test the branches
(name shown; whitespace-only -> 'AI agent' fallback; undefined -> fallback).
Behavior-identical (|| -> ?? since the helper returns null).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
claude code agent 227
ec4622a1b8 test(security): export + unit-test resolveTrustProxy (#105)
Relocate resolveTrustProxy from main.ts (untestable — bootstraps on import) to
integrations/environment/trust-proxy.util.ts and import it back. Unit-test every
branch (empty/undefined -> safe loopback/private default; true/false; hop count;
trim; CIDR/negative passthrough) so a regression can't silently re-open the XFF
spoofing hole (#61).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
claude code agent 227
33c52045a2 test(share-ai): drive the non-text message-part 400 path (#103)
Covers the #63 guard: a message with a non-text part -> 400 'Unsupported message
content'; a message mixing text + a non-text part still 400s (before the 413
size check).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
claude code agent 227
85db20f9f2 test(page): cover movePage server-side cycle guard (#102)
Adds the missing tests for the #67 guard: self-move and a destination inside the
moved page's subtree both throw BadRequestException before updatePage; a
legitimate move proceeds. Mocks pageRepo + spies getPageBreadCrumbs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:52:15 +03:00
084eafd0bb Merge pull request 'fix: review/red-team batch 2 — 30 issues (security, ws, page-templates, html-embed, mcp, tests, docs)' (#101) from fix/review-batch-2 into develop
Reviewed-on: #101
2026-06-21 05:47:05 +03:00
claude code agent 227
455a554054 Merge remote-tracking branch 'gitea/fix/review-batch-2' into fix/review-batch-2
# Conflicts:
#	.env.example
#	README.ru.md
2026-06-21 05:34:17 +03:00
claude code agent 227
7e26239c3f Merge remote-tracking branch 'gitea/develop' into fix/review-batch-2
# Conflicts:
#	AGENTS.md
#	CHANGELOG.md
#	README.md
#	apps/server/src/collaboration/collaboration.handler.ts
#	apps/server/src/common/helpers/prosemirror/html-embed.spec.ts
#	apps/server/src/common/helpers/prosemirror/html-embed.util.ts
#	apps/server/src/core/ai-chat/public-share-chat.service.ts
#	apps/server/src/core/ai-chat/public-share-chat.spec.ts
#	apps/server/src/core/ai-chat/public-share-workspace-limiter.ts
#	apps/server/src/core/page/services/page.service.ts
#	apps/server/src/core/page/transclusion/transclusion.service.ts
#	apps/server/src/integrations/import/services/file-import-task.service.ts
#	apps/server/src/integrations/import/services/import.service.ts
2026-06-21 05:32:44 +03:00
claude_code
bc0c49db05 fix(review): address PR #101 review findings (dead DI, docs)
Some checks failed
Test / test (pull_request) Has been cancelled
- ai-chat: drop the unused pagePermissionRepo injection from
  PublicShareChatToolsService (its only use moved into
  ShareService.resolveReadableSharePage); update all 5 positional
  test construction sites to match the 3-arg constructor.
- env: correct the anonymous share-AI per-workspace cap comment —
  the limiter FAILS CLOSED on Redis failure (#62), not open.
- docs: sync README.ru.md with README.md — move "Page templates"
  from Planned to Done and drop the dead plan-doc link.

Remaining test-coverage gaps tracked as #102, #103, #104, #105, #106.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:24:13 +03:00
claude_code
b5ce51581f docs: add empty state doc for AI chat role cards 2026-06-21 05:23:50 +03:00
claude_code
0fbaebd108 Merge gitea/develop into develop
Some checks failed
Develop / test (push) Has been cancelled
Develop / build (push) Has been cancelled
Reconcile the diverged develop (13 ahead / 20 behind) with gitea/develop.

Conflict resolution — html-embed: keep the local sandboxed-iframe model
(opaque-origin srcdoc, no role-gating) and supersede gitea's same-origin
strip/kill-switch hardening (#26/#28/#29/#30). The 4 conflicted html-embed
source files resolve to the local version; the 3 strip-era spec files stay
deleted. The strip apparatus (stripDisallowedHtmlEmbedNodes,
collectHtmlEmbedSources, canAuthorHtmlEmbed, htmlEmbedAllowed) is fully gone.

Integrate gitea's page-templates / page-embed work (#31-#40) cleanly.

Fix an auto-merge arity mismatch: two new gitea page-template specs
constructed TransclusionService with the pre-sandbox 11-arg signature; drop
the trailing workspaceRepo argument to match the reduced 10-arg constructor.

Verified: server + client tsc --noEmit clean; jest (html-embed + transclusion)
14 suites / 119 tests passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:21:20 +03:00
claude_code
18105ff6db feat(share-ai): label public chat with the assistant identity name
The anonymous public-share "Ask AI" chat labeled every assistant turn
with the generic "AI agent" even when an Assistant identity (agent role)
was configured. Surface the configured identity name instead, falling
back to "AI agent" when no identity is set.

- server: AiSettingsService.resolvePublicShareAssistantName resolves the
  configured role's name (null when unset/missing/disabled), mirroring
  PublicShareChatService.resolveShareRole; ShareController returns it as
  aiAssistantName on /shares/page-info (only when the assistant is on).
- client: thread aiAssistantName -> ShareAiWidget -> MessageList ->
  MessageItem/TypingIndicator via an optional assistantName prop; the
  internal chat omits it and keeps showing "AI agent".
- i18n: add "{{name}} is typing…" (en-US, ru-RU) for the typing line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:01:07 +03:00
claude_code
3936c482d9 refactor(workspace-settings): extract useWorkspaceSetting hook
Deduplicate the "save a workspace setting" plumbing shared by HtmlEmbedSettings
and TrackerSettings (workspace atom read, isLoading state, updateWorkspace + atom
merge forcing settings[key], success/error notifications) into a new
feature-scoped hook useWorkspaceSetting(key).

- Each component keeps its own interaction model: html-embed is an optimistic
  toggle with revert-on-failure; tracker is edit-then-save on an explicit button.
- Unify error handling on the better pattern: surface err.response?.data?.message
  and use console.error (html-embed previously used console.log + a generic message).

No user-facing behavior change; client typecheck clean.

Test-coverage follow-ups (untested trackerHead injection in ShareSeoController and
the no-op audit branch) tracked in #100.
2026-06-21 04:17:54 +03:00
claude code agent 227
a20f4c3876 fix(mcp): close the brute-force limiter check-then-act race (#83)
Some checks failed
Test / test (pull_request) Has been cancelled
isBlocked was checked synchronously but recordFailure ran only AFTER the bcrypt
awaits, so N concurrent /mcp Basic requests for one email all slipped past the
threshold. Add FailedLoginLimiter.tryReserve (atomic synchronous check+increment)
+ release (undo), and reserve all 3 keys BEFORE any await so the (threshold+1)-th
concurrent attempt is rejected before its bcrypt runs. The reservation IS the
failure record (post-await recordFailure removed -> counted exactly once). Non-
credential early throws (missing workspace, SSO/MFA gate) and business errors
release the reservation so they don't burn a victim's budget; success clears.
Tests prove login() runs exactly threshold times under concurrency and that
gate/config rejects don't consume budget.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 04:14:38 +03:00
claude code agent 227
31fcb764d7 refactor(transclusion): unify the ProseMirror collectors into collectNodes (#94)
The three collect*FromPmJson collectors shared the same recursion (and the #55
depth cap) but were copy-pasted, so a future edit could diverge them. Extract a
generic collectNodes(doc, {type, map, key, lastWins, skipChildrenOfType}) and
reimplement all three on it, byte-output-identical (transclusions last-wins;
references/embeds first-wins + transclusionSource skip). Documents (not removes)
the write-only page_template_references graph and the near-duplicate client
lookup-context as tracked follow-ups, per the issue's guidance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 04:04:09 +03:00
claude code agent 227
3f46496192 refactor(share): single resolveReadableSharePage for the share access boundary (#92)
The '(shareId,pageId) -> usable non-restricted page in THIS share' boundary was
written as 3 must-be-identical async sequences. They weren't: the chat funnel
omitted an explicit page.deletedAt check (latently safe via getShareForPage's
CTE) and layered isSharingAllowed separately. Add ShareService.resolveReadable-
SharePage(shareId,pageId,workspaceId) running the single canonical sequence
(getShareForPage -> id match (skipped when null) -> findById -> !deletedAt ->
!hasRestrictedAncestor) returning {share,page}|null; getSharedPage, the funnel,
and the getSharePage tool all use it. hasRestrictedAncestor now lives in the one
method no caller can skip; the funnel still returns uniform 404s and keeps
isSharingAllowed. Adds a direct security-invariant test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 04:04:09 +03:00
claude_code
cecb560fce docs(git-sync): add implementation spec for embedding docmost-sync
Detailed, signature-grounded implementation spec for the native in-process
git-sync feature: GitmostDataSource adapter mapping the engine's DocmostClient
subset onto PageRepo/SpaceRepo/PageService and the collab openDirectConnection
write path, per-space settings + UI, 'git-sync' provenance, Redis leader-lock,
event-driven + interval scheduling, repo-per-space vault topology, phasing A-D,
testing (round-trip idempotency gate vs editor-ext schema), risks and a
file-by-file change checklist. Specced against docmost-sync gitea main (b03eb35).
2026-06-21 03:56:33 +03:00