includio-cms 0.14.4 → 0.14.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/DOCS.md +1 -1
  3. package/ROADMAP.md +11 -0
  4. package/dist/admin/client/account/profile-section.svelte +2 -2
  5. package/dist/admin/client/admin/admin-after-login-layout-content.svelte +12 -1
  6. package/dist/admin/client/collection/collection-entries.svelte +48 -4
  7. package/dist/admin/client/login/login-page.svelte +4 -0
  8. package/dist/admin/client/login/reset-password-page.svelte +4 -0
  9. package/dist/admin/client/maintenance/maintenance-page.svelte +12 -0
  10. package/dist/admin/client/users/accept-invite-page.svelte +4 -0
  11. package/dist/admin/client/users/users-page.svelte +10 -0
  12. package/dist/admin/components/fields/media-field.svelte +203 -253
  13. package/dist/admin/components/fields/relation-field.svelte +4 -9
  14. package/dist/admin/components/fields/url-field.svelte +42 -18
  15. package/dist/admin/components/layout/nav-footer.svelte +12 -9
  16. package/dist/admin/components/media/file/file-details.svelte +115 -18
  17. package/dist/admin/components/media/file/file-miniature.svelte +2 -2
  18. package/dist/admin/components/media/media-selector.svelte +9 -8
  19. package/dist/admin/remote/media.remote.d.ts +6 -4
  20. package/dist/admin/remote/media.remote.js +17 -1
  21. package/dist/admin/utils/debounce.d.ts +1 -0
  22. package/dist/admin/utils/debounce.js +8 -0
  23. package/dist/admin/utils/entryThumbnail.d.ts +6 -1
  24. package/dist/admin/utils/entryThumbnail.js +22 -13
  25. package/dist/admin/utils/pageTitle.d.ts +8 -0
  26. package/dist/admin/utils/pageTitle.js +15 -0
  27. package/dist/cms/runtime/types.d.ts +4 -4
  28. package/dist/core/cms.d.ts +1 -0
  29. package/dist/core/cms.js +2 -0
  30. package/dist/core/server/media/operations/batchRegenerateVideoPosters.d.ts +12 -0
  31. package/dist/core/server/media/operations/batchRegenerateVideoPosters.js +46 -42
  32. package/dist/core/server/media/operations/regenerateVideoPoster.d.ts +5 -0
  33. package/dist/core/server/media/operations/regenerateVideoPoster.js +17 -0
  34. package/dist/core/server/media/operations/replaceFile.js +8 -4
  35. package/dist/core/server/media/operations/uploadFile.js +7 -3
  36. package/dist/core/server/media/styles/operations/batchGenerateStyles.js +11 -0
  37. package/dist/core/server/media/utils/generateAdminThumbnail.d.ts +3 -0
  38. package/dist/core/server/media/utils/generateAdminThumbnail.js +33 -0
  39. package/dist/db-postgres/schema/imageStyle.js +5 -4
  40. package/dist/db-postgres/schema/videoStyle.js +2 -4
  41. package/dist/files-local/video.js +3 -18
  42. package/dist/types/cms.d.ts +1 -0
  43. package/dist/updates/0.14.5/index.d.ts +2 -0
  44. package/dist/updates/0.14.5/index.js +11 -0
  45. package/dist/updates/0.14.6/index.d.ts +2 -0
  46. package/dist/updates/0.14.6/index.js +21 -0
  47. package/dist/updates/index.js +3 -1
  48. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,36 @@
3
3
  All notable changes to includio-cms are documented here.
4
4
  Generated from `src/lib/updates/` — do not edit manually.
5
5
 
6
+ ## 0.14.6 — 2026-04-13
7
+
8
+ Admin page titles — meaningful <title> in every admin route
9
+
10
+ ### Added
11
+ - CMS config: new optional `sidebarHelp` flag (default `true`) — set to `false` to hide the "Help" link in the admin sidebar footer
12
+
13
+ ### Fixed
14
+ - Video posters are now always generated from the first frame — eliminates the visible "jump" when the player starts at 0s after showing a poster taken from a later frame. Existing posters can be rebuilt via Maintenance → "Regenerate video posters"
15
+ - Media library → file details: new "Regenerate from video" button for video files to rebuild the poster/thumbnail on demand (e.g. after replacing a file or when the automatic poster looked wrong)
16
+ - Admin routes now render a descriptive <title> derived from the breadcrumbs trail (e.g. "Entry label · Collection · AriaCMS") instead of showing the raw URL in the browser tab
17
+ - Pre-login routes (login, password reset, accept invite) set their own titles
18
+ - Brand "AriaCMS" used across admin titles (replaces "Includio CMS")
19
+ - Media field: video preview now renders in the same aspect-square box as image preview (checkered background, object-contain) instead of a tall full-width player
20
+ - Media field: unified layout — Change/Remove buttons sit below the preview box for both images and videos (no more overlay covering the image)
21
+ - Media field: video previews now show the file-type icon in the top-right corner (parity with image preview)
22
+ - Media field: accessibility status is now surfaced as a compact informational badge in the top-left corner of the video preview with a tooltip describing what is missing (transcript, audio description); transcript/audio-description management was moved to the file details panel in the media library (single source of truth per file)
23
+ - Relation field: fixed missing space between "Select" and collection label in the picker trigger (e.g. "Select Price category" instead of "SelectPrice category")
24
+
25
+ ## 0.14.5 — 2026-04-10
26
+
27
+ Schema: unique indexes moved into Drizzle ORM definitions
28
+
29
+ ### Added
30
+ - Image & video style unique indexes now defined in Drizzle schema (single source of truth, no more SQL-only index comments)
31
+
32
+ ### Notes
33
+
34
+ No SQL migration needed — indexes already exist in DB. This only moves definitions from SQL comments into Drizzle ORM schema.
35
+
6
36
  ## 0.14.4 — 2026-03-31
7
37
 
8
38
  Improved type generation — select/checkboxes emit union types, fields with defaultValue are non-optional
package/DOCS.md CHANGED
@@ -1,4 +1,4 @@
1
- # Includio CMS Documentation (v0.14.4)
1
+ # Includio CMS Documentation (v0.14.6)
2
2
 
3
3
  > This file is auto-generated from the docs site. For the latest version, update the package.
4
4
 
package/ROADMAP.md CHANGED
@@ -276,6 +276,17 @@
276
276
  - [x] `[feature]` `[P2]` Configurable maintenance interval via `media.maintenance` config <!-- files: src/lib/types/cms.ts -->
277
277
  - [x] `[feature]` `[P1]` Maintenance page UI redesign — status banner, sections, SSE handler dedup <!-- files: src/lib/admin/client/maintenance/maintenance-page.svelte -->
278
278
 
279
+ ## 0.14.5 — Schema cleanup
280
+
281
+ - [x] `[chore]` `[P2]` Move image & video style unique indexes into Drizzle ORM schema (single source of truth) <!-- files: src/lib/db-postgres/schema/imageStyle.ts, src/lib/db-postgres/schema/videoStyle.ts -->
282
+
283
+ ## 0.14.6 — Admin page titles
284
+
285
+ - [x] `[fix]` `[P2]` Admin `<title>` from breadcrumbs + pre-login titles + AriaCMS brand <!-- files: src/lib/admin/utils/pageTitle.ts, src/lib/admin/client/admin/admin-after-login-layout-content.svelte -->
286
+ - [x] `[fix]` `[P1]` Video poster always from first frame (no jump on load) <!-- files: src/lib/files-local/video.ts -->
287
+ - [x] `[feature]` `[P2]` Manual poster regeneration in media library file details <!-- files: src/lib/core/server/media/operations/regenerateVideoPoster.ts, src/lib/admin/components/media/file/file-details.svelte -->
288
+ - [x] `[feature]` `[P2]` Config flag `sidebarHelp` to hide admin sidebar Help link (default true) <!-- files: src/lib/types/cms.ts, src/lib/core/cms.ts, src/routes/admin/(afterLogin)/+layout.server.ts, src/lib/admin/components/layout/nav-footer.svelte -->
289
+
279
290
  ## 0.15.0 — SEO module
280
291
 
281
292
  - [ ] `[feature]` `[P1]` SERP preview + character limits for title/description <!-- files: src/lib/admin/components/fields/seo-field.svelte -->
@@ -88,7 +88,7 @@
88
88
 
89
89
  <!-- Email readonly -->
90
90
  <div class="flex flex-col gap-1">
91
- tttttt<!-- svelte-ignore a11y_label_has_associated_control -->
91
+ <!-- svelte-ignore a11y_label_has_associated_control -->
92
92
  <label class="text-sm font-medium">{lang.profile.email}</label>
93
93
  <div class="acct-input-wrap">
94
94
  <Input
@@ -119,7 +119,7 @@ tttttt<!-- svelte-ignore a11y_label_has_associated_control -->
119
119
 
120
120
  <!-- Role badge -->
121
121
  <div class="flex flex-col gap-1">
122
- tttttt<!-- svelte-ignore a11y_label_has_associated_control -->
122
+ <!-- svelte-ignore a11y_label_has_associated_control -->
123
123
  <label class="text-sm font-medium">{lang.profile.role}</label>
124
124
  <div>
125
125
  <span
@@ -5,13 +5,24 @@
5
5
  import SiteHeader from '../../components/layout/site-header.svelte';
6
6
  import { Breadcrumbs, setBreadcrumbs } from '../../state/breadcrumbs.svelte.js';
7
7
  import { ContentLanguage, setContentLanguage } from '../../state/content-language.svelte.js';
8
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
9
+ import { formatTitle } from '../../utils/pageTitle.js';
8
10
 
9
11
  let { languages, children }: { languages: string[]; children: Snippet } = $props();
10
12
 
11
- setBreadcrumbs(new Breadcrumbs());
13
+ const breadcrumbs = new Breadcrumbs();
14
+ setBreadcrumbs(breadcrumbs);
12
15
  setContentLanguage(new ContentLanguage(languages, languages[0]));
16
+
17
+ const interfaceLanguage = useInterfaceLanguage();
18
+ const fallback = $derived(interfaceLanguage.current === 'pl' ? 'Panel' : 'Admin');
19
+ const title = $derived(formatTitle(breadcrumbs.state, fallback));
13
20
  </script>
14
21
 
22
+ <svelte:head>
23
+ <title>{title}</title>
24
+ </svelte:head>
25
+
15
26
  <Sidebar.Provider style="--sidebar-width: 16rem; --header-height: calc(var(--spacing) * 13);">
16
27
  <AppSidebar />
17
28
 
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { getRawCollectionEntryLabel } from '../../utils/entryLabel.js';
3
- import { getEntryThumbnail } from '../../utils/entryThumbnail.js';
3
+ import { getEntryThumbnail, type MediaThumbnailLookup } from '../../utils/entryThumbnail.js';
4
4
  import { arrayMove } from '../../utils/arrayMove.js';
5
5
  import { getRemotes } from '../../../sveltekit/index.js';
6
6
  import type { CollectionConfigWithType } from '../../../types/collections.js';
@@ -246,11 +246,17 @@
246
246
  // Track relation labels
247
247
  let labelLookup = $state<Record<string, string>>({});
248
248
 
249
+ // Track media thumbnails for grid/list view
250
+ let mediaLookup = $state<MediaThumbnailLookup>({});
251
+
249
252
  $effect(() => {
250
253
  if (entriesQuery.current) {
251
254
  fetchRelationLabels(entriesQuery.current.entries).then((l) => {
252
255
  labelLookup = l;
253
256
  }).catch(() => {});
257
+ fetchMediaThumbnails(entriesQuery.current.entries).then((m) => {
258
+ mediaLookup = m;
259
+ }).catch(() => {});
254
260
  }
255
261
  });
256
262
 
@@ -498,7 +504,45 @@
498
504
  return lookup;
499
505
  }
500
506
 
501
- function mapEntryToRow(entry: RawEntry, lookup: Record<string, string> = {}): CollectionDataTableRow {
507
+ async function fetchMediaThumbnails(entries: RawEntry[]): Promise<MediaThumbnailLookup> {
508
+ const ids = new Set<string>();
509
+ for (const entry of entries) {
510
+ const data = getVersionData(entry);
511
+ if (!data) continue;
512
+ collectMediaUuids(data, ids);
513
+ }
514
+ if (ids.size === 0) return {};
515
+
516
+ const files = await remotes.getMediaFiles({
517
+ data: { ids: Array.from(ids) }
518
+ });
519
+ const lookup: MediaThumbnailLookup = {};
520
+ for (const file of files) {
521
+ if (file.type === 'image') {
522
+ lookup[file.id] = file.thumbnailUrl ?? file.url;
523
+ }
524
+ }
525
+ return lookup;
526
+ }
527
+
528
+ function collectMediaUuids(obj: unknown, ids: Set<string>, depth = 0): void {
529
+ if (depth > 5 || obj === null || obj === undefined) return;
530
+ if (typeof obj === 'string' && UUID_RE.test(obj)) {
531
+ ids.add(obj);
532
+ return;
533
+ }
534
+ if (Array.isArray(obj)) {
535
+ for (const item of obj) collectMediaUuids(item, ids, depth + 1);
536
+ return;
537
+ }
538
+ if (typeof obj === 'object') {
539
+ for (const val of Object.values(obj as Record<string, unknown>)) {
540
+ collectMediaUuids(val, ids, depth + 1);
541
+ }
542
+ }
543
+ }
544
+
545
+ function mapEntryToRow(entry: RawEntry, lookup: Record<string, string> = {}, mediaThumbs: MediaThumbnailLookup = {}): CollectionDataTableRow {
502
546
  const data = getVersionData(entry) || {};
503
547
  // For custom columns, prefer published data (complete) over draft (may be partial)
504
548
  const lang = contentLanguage.current;
@@ -543,7 +587,7 @@
543
587
  status: getEntryStatus(entry),
544
588
  createdAt: new Date(entry.createdAt),
545
589
  updatedAt: new Date(entry.updatedAt),
546
- thumbnail: getEntryThumbnail(data as Record<string, unknown>, collection),
590
+ thumbnail: getEntryThumbnail(data as Record<string, unknown>, collection, mediaThumbs),
547
591
  searchText: getSearchText(entry),
548
592
  a11yWarnings: countA11yWarnings(entry),
549
593
  customData
@@ -658,7 +702,7 @@
658
702
  </div>
659
703
  {:else if entriesQuery.current}
660
704
  {@const result = entriesQuery.current}
661
- {@const allRows = result.entries.map((e) => mapEntryToRow(e, labelLookup))}
705
+ {@const allRows = result.entries.map((e) => mapEntryToRow(e, labelLookup, mediaLookup))}
662
706
  {@const filteredRows = (() => {
663
707
  let rows = allRows;
664
708
  // Client-side status filter (non-archived statuses)
@@ -7,6 +7,10 @@
7
7
  const interfaceLanguage = useInterfaceLanguage();
8
8
  </script>
9
9
 
10
+ <svelte:head>
11
+ <title>{loginLang[interfaceLanguage.current].login.form_label} · AriaCMS</title>
12
+ </svelte:head>
13
+
10
14
  <!-- Skip link (WCAG) -->
11
15
  <a
12
16
  href="#main-content"
@@ -87,6 +87,10 @@
87
87
  }
88
88
  </script>
89
89
 
90
+ <svelte:head>
91
+ <title>{lang.reset.form_label} · AriaCMS</title>
92
+ </svelte:head>
93
+
90
94
  <!-- Skip link (WCAG) -->
91
95
  <a
92
96
  href="#main-content"
@@ -17,6 +17,18 @@
17
17
  import Button from '../../../components/ui/button/button.svelte';
18
18
  import * as Card from '../../../components/ui/card/index.js';
19
19
  import { toast } from 'svelte-sonner';
20
+ import { getBreadcrumbs } from '../../state/breadcrumbs.svelte.js';
21
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
22
+ import { sidebarLang } from '../../components/layout/lang.js';
23
+
24
+ const breadcrumbs = getBreadcrumbs();
25
+ const interfaceLanguage = useInterfaceLanguage();
26
+
27
+ $effect(() => {
28
+ breadcrumbs.state = [
29
+ { label: sidebarLang[interfaceLanguage.current].main.maintenance }
30
+ ];
31
+ });
20
32
 
21
33
  interface GcReport {
22
34
  imageStylesCount: number;
@@ -95,6 +95,10 @@
95
95
  }
96
96
  </script>
97
97
 
98
+ <svelte:head>
99
+ <title>{lang.invite.acceptInvite} · AriaCMS</title>
100
+ </svelte:head>
101
+
98
102
  <!-- Skip link (WCAG) -->
99
103
  <a
100
104
  href="#main-content"
@@ -21,6 +21,8 @@
21
21
  import * as Table from '../../../components/ui/table/index.js';
22
22
  import { authClient } from '../../auth-client.js';
23
23
  import { usersLang } from './lang.js';
24
+ import { sidebarLang } from '../../components/layout/lang.js';
25
+ import { getBreadcrumbs } from '../../state/breadcrumbs.svelte.js';
24
26
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
25
27
  import { toLocaleCode } from '../../utils/formatDate.js';
26
28
  import { getRoleLabel } from '../../utils/roleLabel.js';
@@ -48,6 +50,14 @@
48
50
 
49
51
  const interfaceLanguage = useInterfaceLanguage();
50
52
  const lang = $derived(usersLang[interfaceLanguage.current]);
53
+ const breadcrumbs = getBreadcrumbs();
54
+
55
+ $effect(() => {
56
+ breadcrumbs.state = [
57
+ { label: sidebarLang[interfaceLanguage.current].main.users }
58
+ ];
59
+ });
60
+
51
61
  const session = authClient.useSession();
52
62
  const currentUserId = $derived(session.value?.data?.user?.id ?? '');
53
63