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.
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.
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>
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>
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>
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>
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.
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.
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.
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.
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>
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.
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.
- 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>
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>
Authentik n'expose pas un scope 'groups' standard — demander ce scope
inconnu peut faire echouer l'authorize selon la config provider. Les
groups arrivent dans le claim 'groups' du scope 'profile' par defaut.
Defaut passe de 'openid email profile groups' vers 'openid email profile'.
Update env.example + ACADENICE_PATCHES.md doc associee.
Adds SAML_DISABLE_REQUESTED_AUTHN_CONTEXT env var, passed through
to the SAML strategy's disableRequestedAuthnContext option.
Defaults to existing behavior (element sent). Set to true to omit
the element when the IdP authenticates the user with a method that
does not match (e.g. MFA, FIDO, passwordless), which would
otherwise cause AADSTS75011 with Microsoft Entra ID.