Commit graph

1051 commits

Author SHA1 Message Date
731d7f5e93 feat(sync-blocks): SSE endpoint for realtime updates
Client use-sync-block-realtime opened GET /v1/sync-blocks/:id/events
but no such route existed -> SPA fallback served HTML 200 and the
EventSource failed. Add @Sse(':id/events') wired to the existing
EventEmitter2 broadcast (SYNC_BLOCK_UPDATED_EVENT), filtered per
masterId, with a 25s heartbeat. Auth via the controller JwtAuthGuard
(JWT strategy reads the authToken cookie EventSource sends).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 14:31:50 +00:00
7a11ff4e85 feat(database-view): add row creation
The client only supported editing existing rows (use-update-row =
PATCH only); a freshly created table had no rows and no way to add
one. Add useCreateRow (POST /api/v1/tables/:tableId/rows, bridge
already supports it) and an 'Ajouter une ligne' button in the grid
renderer, gated by canWriteRows, with view-cache invalidation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 14:31:50 +00:00
5e0f5cf49e fix(backlinks): navigate via buildPageUrl so reference clicks work
handleNavigate hand-built /<space>/page/<slugId>, a route that does
not exist (real route is /s/<space>/p/<slug-id>), so clicking any
linked reference did nothing. Use the canonical buildPageUrl helper,
same as the wikilink node.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 11:42:00 +00:00
a1f2ee9e0a feat(backlinks): workspace reindex backfill and sub-page references
Add POST /v1/pages/backlinks/reindex to rebuild acadenice_backlink for
every non-deleted page of the workspace in one call; the per-save
indexer never backfills pre-existing content so graph + backlinks stay
empty otherwise. Surface direct sub-pages in the linked references
panel via a parent_child group sourced live from pages.parent_page_id
(same hierarchy the Knowledge Graph uses), giving Notion-like parity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 09:22:32 +00:00
fe75ea5c45 fix(database-view): send tableId to bridge and correct SSE path
useViewData omitted the tableId query param required by the bridge
GET /views/:id/data route -> 400 'tableId query param required' and a
blank 'Could not load view'. The SSE consumer hit /api/v1/events/sse
but the bridge mounts the stream at /api/events -> 404 reconnect loop.
Thread tableId through ViewDataParams and all five callers; point the
SSE URL at /api/events.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 09:20:38 +00:00
a23f836358 fix(database-view): resolve default Grid view on insert
After createTable, fetch the auto-created views via the new
GET /admin/tables/:tableId/views endpoint and use the Grid view id
when inserting the embed node. Previously viewId was an empty string,
so the renderer always rendered 'No rows found in this view'.
2026-05-12 09:08:03 +00:00
3c1b7a094d fix(backlinks): match database-view node type in kebab-case
The shared schema in @docmost/editor-ext registers the node as
'database-view'; the parser was comparing against the camelCase
form so database_embed links were never extracted.
2026-05-12 09:08:03 +00:00
60654d5d2f fix(backlinks): read workspaceId from camelCased Kysely row
Kysely's CamelCasePlugin (configured globally) rewrites snake_case
columns to camelCase on result rows, so result.rows[0].workspace_id
was undefined and the subsequent insert hit UNDEFINED_VALUE, leaving
acadenice_backlink empty and the Knowledge Graph blank.
2026-05-12 09:08:03 +00:00
43a70929ec fix(database-view): pick a database before listing tables
The insert-database modal called the public bridge /api/v1/tables route,
which requires a databaseId and a Baserow user JWT — the modal supplied
neither, so the request returned 400 then 501.

Add a step 0 to pick a workspace and database (auto-resolved when each
lists only one), then list tables via the admin endpoint
GET /api/v1/admin/tables?databaseId=X. The client extension is also
rewired to .extend() the shared DatabaseView node.
2026-05-11 12:28:38 +00:00
a87e61e382 fix(wikilink): navigate to real page URL and extend shared node schema
Resolve wikilinks to /s/<spaceSlug>/p/<slugId> via buildPageUrl instead of
the inexistent /page/<uuid> route. Persist slugId and spaceSlug as node
attributes so the URL can be rebuilt from the document alone, without an
extra lookup round-trip.

The client extension now .extend()s the shared WikilinkNode from
@docmost/editor-ext to keep the schema identical between client and the
Hocuspocus server.
2026-05-11 12:28:23 +00:00
b802f1d647 feat(editor-ext): share wikilink and database-view node schemas
Add WikilinkNode and DatabaseView schema-only nodes to @docmost/editor-ext
and register them in the Hocuspocus server tiptapExtensions list.

Without the shared schema, jsonToNode on the server hit a RangeError for
those node types and stripUnknownNodes dropped them on every collab save,
so wikilinks disappeared on page reload and database-view embeds lost
their config and rendered as empty placeholders.
2026-05-11 12:28:12 +00:00
dbd79cc17c fix(page-permission): export RestrictionInfo to satisfy TS4053
Public controller method returns Promise<RestrictionInfo>, the
interface must be exported from the service for tsc to name it
in the controller's declaration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:59:53 +00:00
91eee92282 feat(navigation): replace EE-only pages with Acadenice OSS equivalents
Routes /ai, /ai/chat, /templates, /settings/audit, /settings/api-keys,
/settings/account/api-keys, /settings/ai, /settings/verifications now
point at the Acadenice OSS replacements (TemplatesAdminPage,
AcadeniceAuditLogPage, AcadeniceApiKeysPage) — or are removed when the
feature is intentionally dropped from the OSS build. Settings sidebar
entries follow the same change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:54:54 +00:00
f2e9d2205c refactor(rbac): seed service updates and spec alignment
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:54:46 +00:00
11e003e71e refactor(graph): camelCase row keys and drop dead try/catch lazy import
Server: rows from kysely with camelCase plugin already arrive as
sourcePageId / targetPageId / spaceId / spaceName. Drop the snake_case
indexing and update the spec accordingly.

Client: remove the unreachable try/catch around React.lazy for
react-force-graph-2d — lazy() never throws synchronously, the catch
was dead code from an earlier wip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:54:39 +00:00
47dee1eb12 refactor(client): unwrap .data from TransformHttpResponseInterceptor
The server-side TransformHttpResponseInterceptor wraps every body in
{ data, success, status }. The axios interceptor only unwraps the
transport layer, so calls return that envelope object — read .data
to get the actual payload.

Aligns the remaining Acadenice REST clients (backlinks, clipper,
slash-commands, sync-blocks, templates, graph, rbac) with the
existing convention and drops a few hardcoded /api/v1 prefixes that
duplicated the api baseURL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:54:32 +00:00
843986d5c2 feat(database-view): admin UI and create-database slash command
Adds two new entry points around the bridge-backed database view:
- /database admin modal to manage fields (field-admin-modal)
- slash command to create a database from the editor
  (create-database-modal + create-database-slash)
Wires the new components into the editor slash menu and the
database-view module index.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:54:06 +00:00
41ce6308fa feat(page-permission): OSS-ify page-level permission module
Adds a native page-permission controller + service under
apps/server/src/core/page/page-permission, wired into PageModule.

LicenseCheckService now declares PAGE_PERMISSIONS and SHARING_CONTROLS
as Acadenice OSS features so hasFeature() / resolveFeatures() always
expose them regardless of EE plan, keeping useHasFeature() and the
server-side guards consistent.

tsconfig.build.json excludes vitest.config.ts from the Nest build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:54:00 +00:00
9e686af2e3 chore: gitignore .pnpm-store
Local pnpm content-addressable store should not be tracked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:53:48 +00:00
730e52acd2 fix(build): skip extension-clipper build to unblock Docker build
Why: clipper Vite build fails on missing icons/icon16.png asset.
We don't ship the clipper inside the main image — overriding its
build script to a no-op lets pnpm build complete without touching
the clipper workspace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:53:41 +00:00
d120619245 docs(acadedoc): update ACADENICE_PATCHES.md with R5.3 Swagger entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:37:13 +02:00
21ce2a94c7 feat(acadedoc): add OpenAPI Swagger documentation for /api/v1/* — R5.3
Install @nestjs/swagger@^11.4.2 + nestjs-zod@^5.3.0. Bootstrap SwaggerModule
in main.ts with cleanupOpenApiDoc (dev/staging only; SWAGGER_ENABLED=true for
prod). Add @ApiTags, @ApiBearerAuth, @ApiOperation, @ApiResponse, @ApiParam,
@ApiQuery, @ApiBody decorators to all 16 acadenice /v1/* controllers.
Add swagger.spec.ts (8 tests green). Add docs/api-docs.md (SDK gen guide).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:36:14 +02:00
a39c158748 refactor(acadedoc): audit REST conventions + fix pre-existing tests — R5.2
BREAKING CHANGES (row-comments routes):
- POST /v1/row-comments/list     → GET  /v1/row-comments (query params)
- POST /v1/row-comments/create   → POST /v1/row-comments (201 Created)
- POST /v1/row-comments/update   → PATCH /v1/row-comments/:id
- POST /v1/row-comments/resolve  → PATCH /v1/row-comments/:id/resolve
- POST /v1/row-comments/delete   → DELETE /v1/row-comments/:id (204)
- POST /v1/row-comments/count    → GET /v1/row-comments/count (query params)
- UpdateRowCommentDto/ResolveRowCommentDto: removed commentId field (now path param)

REST patches (non-breaking):
- POST /v1/sync-blocks: added explicit @HttpCode(201)
- POST /v1/slash-commands: added explicit @HttpCode(201)
- POST /v1/templates: added explicit @HttpCode(201)
- POST /v1/templates/:id/instantiate: added explicit @HttpCode(201)

Pre-existing test fixes:
- clipper-client.test.ts: jest.mock/jest.fn → vi.mock/vi.fn (Vitest compat)
- templates-client.ts + clipper-client.ts + slash-commands-client.ts
  + sync-blocks-client.ts: removed double-unwrap .data.data → .data

Tests: 366/366 client vitest pass, 448/453 server jest pass (5 pre-existing infra failures)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:33:54 +02:00
9dd283ced6 refactor(acadedoc): rename API routes /api/acadenice -> /api/v1 — R5.1
Replace all @Controller('acadenice/...') decorators with 'v1/...' on 16 NestJS controllers. Update all client services, hooks, tests, extension-clipper, and doc comments to match. DB table names (acadenice_*) and folder structure untouched.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 14:52:49 +02:00
3af579498b fix(acadenice): filter undefined pageIds before sql.lit() in graph — Patch 030
loadPageMeta logged 'invalid immediate value undefined' on every graph
request. Cause: when an edge row has a null source/target_page_id (rare,
but happens during partial backlink reindex), the resulting finalPageIds
set carries undefined entries, and sql.lit(undefined) rejects.

Filter cleanIds to string-only and return early if empty. The catch
block stays as a safety net for unrelated SQL errors.

Verified via curl: GET /api/acadenice/graph now returns 200 with no
ERROR log line.

Patch 030.
2026-05-08 14:08:34 +02:00
243168a3f8 feat(acadenice): wire /template slash command to TemplatePickerModal via DOM event
R4.8 — PageContent now listens for acadenice:open-template-picker on document
and opens TemplatePickerModal via useDisclosure. The slash command dispatched
the event but nothing handled it, so the modal never opened.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 13:16:19 +02:00
9139fb8728 fix(acadenice): pass content object directly to jsonb param + remove empty text nodes — Patch 028
Two bugs in template seed/instantiate:

1. ${JSON.stringify(content)}::jsonb made Postgres store the content as
   a jsonb scalar string (jsonb_typeof = 'string'), not an object. The
   instantiate read it back as a JSON-encoded string, which ProseMirror
   tried to parse as a node tree and crashed with 'Unknown node type:
   undefined' on the outer string. Pass the object directly with
   ${content as unknown as string}::jsonb so postgres-js binds it as
   a JSONB value.

2. Built-in template seed used { type: 'paragraph', content: [{ type:
   'text', text: '' }] } for empty paragraphs / list items / task
   items. ProseMirror schema rejects empty text nodes ('Empty text
   nodes are not allowed'). Replaced with content: [].

Verified via curl: POST /api/acadenice/templates/{id}/instantiate now
returns 201 with the new pageId/slugId.

Patch 028.
2026-05-08 13:01:10 +02:00
8c3d55024b fix(acadenice): use camelCase row keys in sync-block repo — Patch 027
The DatabaseModule registers a global CamelCasePlugin which converts
every column name (including SELECT aliases) from snake_case to
camelCase at runtime. The sync-block repo declared sql<{...}> result
types in snake_case (workspace_id, created_at, etc.) and accessed those
keys in mapRow / findUsages. At runtime kysely returned camelCase keys
so every property read was undefined, causing 'Invalid time value' on
new Date(undefined).toISOString().

Same pattern is likely present in graph.service.ts and backlinks
services — Patch 028 will sweep those.

Verified via curl: POST /api/acadenice/sync-blocks now returns 201 with
the full DTO.

Patch 027.
2026-05-08 12:53:29 +02:00
7fba3c0452 fix(acadenice): sync-block date string + graph p.slug column name — Patch 026
Two server bugs surfaced from the live test:

1. SyncBlockRepo.mapRow crashed with 'Cannot read properties of
   undefined (reading toISOString)' on POST /api/acadenice/sync-blocks.
   The kysely-postgres-js driver returns timestamps as strings, not
   Date instances. Wrap with new Date(...) before .toISOString().

2. GraphService.loadPageMeta + loadOrphanPages SELECT'd p.slug, but the
   native pages table column is named slug_id. Postgres rejected the
   query, the catch returned [], and the graph rendered empty even when
   pages existed. Aliased p.slug_id AS slug to match the PageMetaRow
   interface.

Patch 026.
2026-05-08 12:47:12 +02:00
9b33a2683b fix(client): switch bridge token to import.meta.env (Vite auto-expose) — Patch 025
Patch 023 attempted to read process.env.VITE_BRIDGE_TOKEN, but the
vite.config.ts define block uses { 'process.env': {...} } which only
substitutes the literal expression 'process.env' standalone, not when
followed by a member access like 'process.env.X'. So the runtime call
evaluated to undefined and the bridge interceptor never sent a Bearer
header — every /database call got 401.

Switch to Vite's standard pattern: VITE_BRIDGE_TOKEN lives in
apps/client/.env.local (gitignored, must be created locally) and is
auto-exposed via import.meta.env. Verified: the dev server substitutes
it inline at transform time as 'brg_smoketest_admin'.

Patch 025.
2026-05-08 12:44:18 +02:00
e027ae9357 fix(acadenice): unwrap server response envelope in 4 client services — Patch 024
The server applies a global TransformHttpResponseInterceptor that wraps
every response body in { data, success, status }. Native Docmost client
services use the api axios instance whose interceptor already unwraps
once, so callers see r.data === payload directly.

The acadenice client services use axios directly (no interceptor), so
r.data === { data, success, status } envelope. Calling templates.map()
on the envelope crashed <TemplatePickerModal> with 'templates.map is
not a function' — exact symptom Corentin hit (white screen on click).

Patched 4 services to read r.data.data: templates, sync-blocks,
slash-commands, clipper. The notifications service already uses the api
instance so it was untouched.

A future refactor should migrate all 4 to the shared api instance for
consistency and to inherit auth/redirect handling.

Patch 024.
2026-05-08 12:41:18 +02:00
5f7bce9b02 fix(client): read VITE_BRIDGE_TOKEN via process.env (define block target) — Patch 023
The previous fix (Patch 021) read import.meta.env.VITE_BRIDGE_TOKEN, but
Vite only auto-exposes VITE_* in import.meta.env when the .env lives in
apps/client/. Our .env is at the monorepo root and is loaded via
loadEnv() in vite.config.ts, then injected through the define block
under the 'process.env' key. So the runtime variable lives at
process.env.VITE_BRIDGE_TOKEN, not import.meta.env.VITE_BRIDGE_TOKEN.

Without this, the bridge proxy received no Bearer header and returned
401 Unauthorized for every /database slash command request — exactly
what Corentin reported.

Patch 023.
2026-05-08 12:34:38 +02:00
aef912f9a4 fix(client): bridge same-origin proxy via Vite + dev token fallback — Patch 021
Cross-origin (5173 vs 4000) blocked the auth cookie from reaching the
bridge — every /database slash command request 401'd silently. Two
changes:

1. vite.config.ts: add /bridge -> http://localhost:4000 proxy with
   path rewrite + /bridge-events -> /api/events for SSE. Same-origin =
   browser sends the auth cookie automatically.
2. bridge-client.ts: resolveBridgeUrl() defaults to /bridge instead
   of absolute http://localhost:4000. Also adds a VITE_BRIDGE_TOKEN env
   fallback for dev (the bridge requires Bearer auth and the Docmost
   session cookie is HttpOnly so JS cannot read it).

Patch 021.
2026-05-08 12:32:33 +02:00
5b512e6324 feat(acadedoc): replace EE Settings with open source UI (audit log, API keys, OIDC status) — R4.5
Server (NestJS):
- AcadeniceAuditLogModule: GET /api/acadenice/audit-log (admin/owner, Kysely, paginated + filtered)
- AcadeniceApiKeysModule: GET/POST/DELETE /api/acadenice/api-keys (JWT, bcrypt hash, acdk_ prefix)
- AcadeniceSecurityModule: GET /api/acadenice/security/oidc-status (admin, no secrets exposed)
- Migration 20260510T100000: acadenice_api_key table with token_hash + bcrypt
- Permissions catalog: added audit_log:read

Client (React 18 + Mantine v7):
- Audit log page: paginated table with filters (event, userId, date range)
- API keys page: list/create/revoke personal tokens, one-time display modal
- Security/OIDC status page: read-only, env-var config reference
- Sidebar rewired: Security & SSO, API keys, Audit log -> acadenice/* routes (no EE feature gates)
- Prefetch functions for new routes

Tests: 36 server (Jest) + client typecheck clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 12:24:00 +02:00
38f7d73e85 fix(acadenice): hoist useDisclosure above early return — Patch 022
SpaceSidebar crashed with "Rendered more hooks than during the previous
render" because R3.6 added a useDisclosure() AFTER `if (!space) return`.
When `space` flipped from undefined (loading) to defined (loaded), the
hook count changed and React threw, triggering the Error Boundary and
blanking the page.

This single bug was the root cause of 5 reported failures (R4.7 smoke):
create page, sub-page, wikilink, /database, /template, /sync-block —
all blocked by the white screen.

Fix: hoist the hook above the conditional return. Hook order now stable.
2026-05-08 12:21:17 +02:00
60d64822e4 fix(acadenice): include parent-child edges in graph + space-scope view — R4.6
Graph view was empty for users who built page hierarchies (sub-pages) but had
not placed any wikilinks. The graph service now queries pages.parent_page_id
as a second edge source (type: parent_child) and merges it with acadenice_backlink
edges, so hierarchy-only workspaces display meaningful graphs immediately.

- dto: added parent_child to LinkType enum; added slug field to GraphNode
- service: loadParentChildEdges (permission-filtered SQL), parallel merge with loadEdges
- controller: always appends parent_child to the effective type list
- client: graph-client.ts typed for parent_child and slug; graph-canvas renders
  dashed grey lines for parent_child vs solid brand lines for wikilinks; legend
  with aria-label; title/slug/untitled fallback chain for node labels
- space sidebar: Graph menu item -> /s/:spaceSlug/graph
- new route: /s/:spaceSlug/graph -> SpaceGraph page (injects spaceId filter)
- i18n: en-US + fr-FR keys for legend and space graph
- tests: 42 server + 59 client, all green; 10 new R4.6 tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 12:14:28 +02:00
8e717401bd refactor(acadedoc): move branding from .env to UI-only — Patch 020
Per Corentin's feedback (2026-05-08): .env should be reserved for
server-side config (SMTP, DB, OIDC). Branding (name, colors, logo)
is admin/UI territory.

Changes:
- Remove BRAND_NAME / BRAND_LOGO_URL / BRAND_PRIMARY_COLOR /
  BRAND_ACCENT_COLOR from .env.example, vite.config.ts define block
- Hardcode "AcadeDoc" + #2563eb / #7c3aed as defaults in
  apps/client/src/lib/config.ts and brand-theme.ts
- getBrandTheme() now takes optional runtime overrides instead of
  reading process.env (used by per-workspace branding hook to apply
  DB-stored colors)
- Server getMailFromName() defaults to "AcadeDoc" hardcoded; only
  MAIL_FROM_NAME env var can override
- Fix workspace-branding.spec.ts (was importing vitest in jest project,
  R4.4 leftover bug, similar to Patch 017 scope)
- Fix environment.service.spec.ts (was missing ConfigService provider
  in TestingModule, pre-existing upstream bug surfaced by jest run)

Tests: 13 brand-theme + 13 workspace-branding + 2 environment = 28
green. Per-workspace UI override via /settings/branding (R4.4) works
unchanged.

Patch 020.
2026-05-08 11:49:49 +02:00
23a85267bf feat(acadenice): add sync blocks for cross-page content sharing — R4.2
Implements Notion-style sync blocks: a Tiptap node whose content is shared
across N pages. Editing via the Hocuspocus overlay propagates to all instances
via Yjs collab + SSE broadcast (EventEmitter2 bus).

Server: DB migration, NestJS module (CRUD + BFS cycle detection + broadcast),
Hocuspocus persistence extension extended for sync-block-* docs, 3 new RBAC
permissions (sync_blocks:create/edit/delete), seeded to Admin/Editor/Member.

Client: SyncBlockExtension (Tiptap node), SyncBlockNodeView (NodeView +
Mantine Modal overlay + SSE hook), /sync-block slash command, service client.

Tests: 32 server Jest + 18 client Vitest, all green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:40:12 +02:00
b53ab5043f feat(acadedoc): add AcadeDoc branding, Brevo SMTP preset, UI customization — R4.4
- Rebranding: BRAND_NAME env var (default AcadeDoc) replaces hardcoded "DocAdenice"
  in index.html title/meta, PWA manifest, app-header logo text, email footer/body
- lib/config.ts: getAppName() reads BRAND_NAME; new getBrandLogoUrl/PrimaryColor/AccentColor helpers
- vite.config.ts: BRAND_* vars exposed via define block to client
- brand-theme.ts: getBrandTheme() generates 10-shade MantineColorsTuple from hex
  (no @mantine/colors-generator dep); merged into MantineProvider at boot
- theme/__tests__/brand-theme.test.ts: 11 vitest tests (generateColorTuple + getBrandTheme)
- Workspace branding: migration adds primary_color/accent_color to workspaces table
  WorkspaceBrandingService + WorkspaceBrandingController (POST /workspace/branding,
  POST /workspace/branding/update — admin only) + DTO hex validation
- Settings: /settings/branding page (WorkspaceBranding) + sidebar entry (admin-only)
- workspace-branding.spec.ts: 13 vitest tests (service + controller + DTO validation)
- SMTP Brevo: .env.example preset block + transactional/README.md ops guide
  (key gen, port 587 STARTTLS, 300/day free limit, swaks/curl test)
- environment.service.ts: getMailFromName() falls back to BRAND_NAME if MAIL_FROM_NAME unset
- vitest.config.ts server: include pattern extended to src/core/workspace/spec/**
- i18n: 11 branding keys added to en-US and fr-FR translations
- 0 TypeScript errors client + server, 11 client + 13 server new tests all green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:36:38 +02:00
d0b75774d8 feat(acadenice): add timeline view (Gantt) for databases — R4.1
- New TimelineRenderer using @fullcalendar/timeline + @fullcalendar/resource-timeline
- Column mapping config (title/start/end/resource) persisted in bridge Redis TTL 30d
- useTimelineConfig hook (GET+POST /views/:viewId/timeline-config)
- Inline config panel shown on first use; re-accessible via Configure button
- Resource swimlane mode activated automatically when resourceCol is configured
- eventResize persists endCol via useUpdateRow (respects canWriteRows)
- End date fallback: start + 1 day when endCol is absent
- InsertDatabaseModal extended with step 3 for column mapping on timeline views
- database-view-component dispatches timeline viewType to TimelineRenderer
- 14 client Vitest tests + 12 bridge Vitest tests (26 total new, all green)
- 0 TypeScript errors (client + bridge)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:27:11 +02:00
3c6478826a fix(server): make package.json require resilient to dist mode
Three upstream Docmost services (export, version, telemetry) require
'../../../package.json' relative to source. In nest start dist mode the
relative path resolves one level too short and crashes at boot.

Wrap each require in a try/catch fallback that walks up one extra level,
defaulting to { version: 'dev' } if neither resolves. Boot now succeeds
both in dev (tsx) and in dist (node dist/main).

Also adds docker-compose.dev.yml for an isolated dev stack on ports
5433/6380, kept in repo for future dev sessions.

Patch 018.
2026-05-08 11:15:06 +02:00
44bfd5d616 chore(client): rename vitest config to .mts (ESM) 2026-05-08 10:44:15 +02:00
4cf04080cf fix(acadenice): resolve test suite failures across R3 sub-blocks (Patch 017)
- Convert 17 server spec files from vitest to Jest (vi -> jest globals)
- Add jest.mock stubs for ESM-only prosemirror/html and collaboration modules
- Fix Zod v4 strict UUID validation failures in test fixtures (version byte [1-8] required)
- Add JwtAuthGuard.overrideGuard in all controller specs that lacked it
- Fix jest.Mock type inference (ReturnType<typeof jest.fn> -> jest.Mock) to prevent 'never' arg errors
- Delete vitest.config.ts (CJS), keep vitest.config.mts (ESM-compatible) on client
- Add global mocks for @excalidraw/excalidraw and @/main.tsx in client test-setup
- Result: client 38/38 suites 313/313 tests, server acadenice 21/21 suites 210/210 tests, 0 TS errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 10:36:19 +02:00
be951a22ac feat(acadenice): add inline comments threads for R3.8 (30 tests, Patch 016)
Page comments: REST resolve/unresolve facade over native comments table
(PageCommentResolveService + CollaborationGateway yjs mark sync).
Row comments: new acadenice_row_comment table + full CRUD + resolve
(RowCommentService, RowCommentsController) + React panel in RowDetailModal.
4 new permissions: comments:read/write/resolve/moderate (30 total).
i18n FR+EN. R3 ENTIEREMENT TERMINE.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 02:47:15 +02:00
7d076aa86f feat(acadenice): add mentions notifications system for R3.7 (45 tests, Patch 015)
Bridges native Docmost mention notification pipeline (already active for
collab path) to the REST API path via NotificationEmitterService.  Adds
AcadeniceNotificationsModule with mention detector, notification facade API,
preferences endpoint, /notifications full page, /settings/notifications
preferences page, and bell count polling (30s).  No new DB migration —
native notifications table handles page.user_mention.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 02:29:01 +02:00
614533f228 feat(acadenice): add page templates system for R3.6 (65 tests, Patch 014)
- DB migration: acadenice_template table (JSONB content, is_built_in, is_workspace_default, usage_count)
- 3 new permissions: templates:read|create|manage — catalogue goes to 26
- NestJS AcadeniceTemplatesModule: TemplateService (CRUD + instantiate + setDefault), TemplateSeedService (5 built-ins), TemplatesController (7 endpoints)
- Built-in seed: Meeting Note, Project Brief, Daily Standup, Weekly Review, Empty Page — clone-then-edit pattern
- Frontend: templates-admin gallery (TemplatesPage /settings/templates, TemplateGallery, TemplateCard, TemplateForm)
- Frontend: TemplatePickerModal — opened via "New page from template" sidebar dropdown + /template slash command (custom DOM event)
- i18n: 39 keys FR + EN
- Tests: 40 backend (22 service + 18 controller) + 25 frontend (9 client + 9 page + 7 card) = 65 tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 02:12:58 +02:00
aac0149e7a feat(acadenice): add graph view UI for R3.5.2 (58 tests, Patch 013)
Frontend knowledge-graph page at /graph. Force-directed canvas via
react-force-graph-2d (lazy-loaded, falls back to placeholder when lib
absent). Controls sidebar: space filter, edge-type checkboxes, depth
slider 1-5, include-orphans toggle, node search, stats, truncated
banner. Side panel Drawer on node click: title, space, in/out degree,
open-page CTA. Jotai atoms for cross-component state. React Query
hook with 300ms debounce. Interactions: zoom/pan/drag, click->side
panel, double-click->navigate, right-click->focus, Esc/F keyboard.
Upstream patches: +route /graph in App.tsx, +Graph nav entry in
global-sidebar, +24 i18n keys FR+EN.

New deps (not installed): react-force-graph-2d, d3-force.
Completes R3.5 entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 01:39:13 +02:00
5f7271da19 feat(acadenice): add graph endpoint for R3.5.1
GET /api/acadenice/graph returns { nodes, edges, meta } from acadenice_backlink.
BFS depth-limited traversal, Redis cache TTL 60s, permission-aware SQL aggregation,
truncation at 1000 nodes. 35 tests (21 service + 14 controller). Patch 012.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 01:27:23 +02:00
9be979ee90 feat(acadenice): add dual editor (WYSIWYG + markdown source) for R3.4
Custom bidirectional markdown converter (no new deps) with full round-trip
support for database-view, wikilink, mention nodes. DualEditor component wraps
PageEditor with a toolbar toggle (WYSIWYG<->markdown), lossy-switch modal, and
localStorage persistence per page. 77 tests covering 24 round-trip cases + 4
custom nodes + 9 edge cases. i18n FR+EN.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 01:18:29 +02:00
ba18a349d4 docs(fork): update ACADENICE_PATCHES.md Patch 010 for R3.3 2026-05-08 01:07:22 +02:00