@revenexx/studio-shared 0.1.2 → 0.3.0

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 (120) hide show
  1. package/README.md +22 -12
  2. package/dist/module.d.mts +15 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +58 -0
  5. package/dist/runtime/components/DataTable/ColumnsMenu.d.vue.ts +12 -0
  6. package/{components → dist/runtime/components}/DataTable/ColumnsMenu.vue +13 -19
  7. package/dist/runtime/components/DataTable/ColumnsMenu.vue.d.ts +12 -0
  8. package/dist/runtime/components/DataTable/DataTable.d.vue.ts +87 -0
  9. package/dist/runtime/components/DataTable/DataTable.vue +510 -0
  10. package/dist/runtime/components/DataTable/DataTable.vue.d.ts +87 -0
  11. package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +21 -0
  12. package/dist/runtime/components/DataTable/FilterMenu.vue +46 -0
  13. package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +21 -0
  14. package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +6 -0
  15. package/dist/runtime/components/DataTable/SavedViewsMenu.vue +189 -0
  16. package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +6 -0
  17. package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +13 -0
  18. package/dist/runtime/components/DataTable/SortMenu.vue +47 -0
  19. package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +13 -0
  20. package/dist/runtime/components/DataTable/ViewModeSwitch.d.vue.ts +20 -0
  21. package/dist/runtime/components/DataTable/ViewModeSwitch.vue +26 -0
  22. package/dist/runtime/components/DataTable/ViewModeSwitch.vue.d.ts +20 -0
  23. package/dist/runtime/components/DataTable/types.d.ts +104 -0
  24. package/dist/runtime/components/DataTable/types.js +0 -0
  25. package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +17 -0
  26. package/dist/runtime/components/Media/MediaFolderTree.vue +65 -0
  27. package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +17 -0
  28. package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +18 -0
  29. package/dist/runtime/components/Media/MediaPickerDialog.vue +163 -0
  30. package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +18 -0
  31. package/dist/runtime/components/Media/MediaUploader.d.vue.ts +29 -0
  32. package/dist/runtime/components/Media/MediaUploader.vue +161 -0
  33. package/dist/runtime/components/Media/MediaUploader.vue.d.ts +29 -0
  34. package/dist/runtime/components/Shared/BuildLogsFlyout.d.vue.ts +30 -0
  35. package/{components → dist/runtime/components}/Shared/BuildLogsFlyout.vue +37 -54
  36. package/dist/runtime/components/Shared/BuildLogsFlyout.vue.d.ts +30 -0
  37. package/dist/runtime/components/Shared/GitDeploymentSettings.d.vue.ts +17 -0
  38. package/dist/runtime/components/Shared/GitDeploymentSettings.vue +75 -0
  39. package/dist/runtime/components/Shared/GitDeploymentSettings.vue.d.ts +17 -0
  40. package/dist/runtime/components/Shared/KpiTile.d.vue.ts +13 -0
  41. package/{components → dist/runtime/components}/Shared/KpiTile.vue +11 -19
  42. package/dist/runtime/components/Shared/KpiTile.vue.d.ts +13 -0
  43. package/dist/runtime/components/Shared/ListSurface.d.vue.ts +38 -0
  44. package/dist/runtime/components/Shared/ListSurface.vue +27 -0
  45. package/dist/runtime/components/Shared/ListSurface.vue.d.ts +38 -0
  46. package/dist/runtime/components/Shared/PageHeader.d.vue.ts +23 -0
  47. package/{components → dist/runtime/components}/Shared/PageHeader.vue +6 -8
  48. package/dist/runtime/components/Shared/PageHeader.vue.d.ts +23 -0
  49. package/dist/runtime/components/Shared/RecordLayout.d.vue.ts +52 -0
  50. package/dist/runtime/components/Shared/RecordLayout.vue +63 -0
  51. package/dist/runtime/components/Shared/RecordLayout.vue.d.ts +52 -0
  52. package/dist/runtime/components/Shared/RecordRailCard.d.vue.ts +26 -0
  53. package/dist/runtime/components/Shared/RecordRailCard.vue +20 -0
  54. package/dist/runtime/components/Shared/RecordRailCard.vue.d.ts +26 -0
  55. package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +41 -0
  56. package/dist/runtime/components/Shared/SidebarNav.vue +48 -0
  57. package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +41 -0
  58. package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +15 -0
  59. package/dist/runtime/components/Shared/SidebarNavLink.vue +49 -0
  60. package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +15 -0
  61. package/dist/runtime/components/Shared/SortHeader.d.vue.ts +23 -0
  62. package/dist/runtime/components/Shared/SortHeader.vue +35 -0
  63. package/dist/runtime/components/Shared/SortHeader.vue.d.ts +23 -0
  64. package/dist/runtime/components/Shared/StateCard.d.vue.ts +32 -0
  65. package/{components → dist/runtime/components}/Shared/StateCard.vue +21 -36
  66. package/dist/runtime/components/Shared/StateCard.vue.d.ts +32 -0
  67. package/dist/runtime/components/Shared/StudioCard.d.vue.ts +15 -0
  68. package/dist/runtime/components/Shared/StudioCard.vue +19 -0
  69. package/dist/runtime/components/Shared/StudioCard.vue.d.ts +15 -0
  70. package/dist/runtime/components/Shared/TabBar.d.vue.ts +23 -0
  71. package/{components → dist/runtime/components}/Shared/TabBar.vue +7 -16
  72. package/dist/runtime/components/Shared/TabBar.vue.d.ts +23 -0
  73. package/dist/runtime/components/Shared/ViewTab.d.vue.ts +7 -0
  74. package/{components → dist/runtime/components}/Shared/ViewTab.vue +7 -9
  75. package/dist/runtime/components/Shared/ViewTab.vue.d.ts +7 -0
  76. package/dist/runtime/components/Shared/Wizard.d.vue.ts +47 -0
  77. package/dist/runtime/components/Shared/Wizard.vue +66 -0
  78. package/dist/runtime/components/Shared/Wizard.vue.d.ts +47 -0
  79. package/dist/runtime/composables/useListRail.d.ts +9 -0
  80. package/dist/runtime/composables/useListRail.js +19 -0
  81. package/dist/runtime/composables/useMediaLibrary.d.ts +41 -0
  82. package/dist/runtime/composables/useMediaLibrary.js +36 -0
  83. package/dist/runtime/composables/usePlatformAuth.d.ts +35 -0
  84. package/dist/runtime/composables/usePlatformAuth.js +17 -0
  85. package/dist/runtime/composables/usePlatformTalkback.d.ts +29 -0
  86. package/dist/runtime/composables/usePlatformTalkback.js +54 -0
  87. package/dist/runtime/composables/usePlatformTenant.d.ts +14 -0
  88. package/dist/runtime/composables/usePlatformTenant.js +18 -0
  89. package/dist/runtime/composables/useT.d.ts +11 -0
  90. package/dist/runtime/composables/useT.js +8 -0
  91. package/dist/runtime/composables/useTalkbackChannels.d.ts +78 -0
  92. package/dist/runtime/composables/useTalkbackChannels.js +52 -0
  93. package/dist/runtime/lib/formatBridge.d.ts +50 -0
  94. package/dist/runtime/lib/formatBridge.js +54 -0
  95. package/dist/runtime/lib/i18nBridge.d.ts +31 -0
  96. package/dist/runtime/lib/i18nBridge.js +17 -0
  97. package/dist/runtime/lib/isoLists.d.ts +61 -0
  98. package/dist/runtime/lib/isoLists.js +62 -0
  99. package/dist/runtime/lib/sidebarNav.d.ts +34 -0
  100. package/dist/runtime/lib/sidebarNav.js +22 -0
  101. package/dist/runtime/lib/talkback.d.ts +59 -0
  102. package/dist/runtime/lib/talkback.js +49 -0
  103. package/dist/runtime/types/nav.d.ts +37 -0
  104. package/dist/runtime/types/nav.js +0 -0
  105. package/dist/runtime/types/storage.d.ts +67 -0
  106. package/dist/runtime/types/storage.js +0 -0
  107. package/dist/types.d.mts +27 -0
  108. package/package.json +41 -13
  109. package/components/DataTable/DataTable.vue +0 -518
  110. package/components/DataTable/FilterMenu.vue +0 -40
  111. package/components/DataTable/types.ts +0 -82
  112. package/components/Shared/GitDeploymentSettings.vue +0 -87
  113. package/components/Shared/SidebarNav.vue +0 -100
  114. package/components/Shared/SortHeader.vue +0 -50
  115. package/components/Shared/StudioCard.vue +0 -24
  116. package/components/Shared/Wizard.vue +0 -80
  117. package/composables/usePlatformAuth.ts +0 -35
  118. package/composables/usePlatformTenant.ts +0 -35
  119. package/index.ts +0 -16
  120. package/nuxt.config.ts +0 -23
@@ -0,0 +1,66 @@
1
+ <script setup>
2
+ import { Button } from "@revenexx/studio";
3
+ const props = defineProps({
4
+ steps: { type: Array, required: true },
5
+ modelValue: { type: Number, required: false, default: 0 },
6
+ canAdvance: { type: Boolean, required: false, default: true },
7
+ busy: { type: Boolean, required: false, default: false }
8
+ });
9
+ const emit = defineEmits(["update:modelValue", "finish"]);
10
+ const t = useT();
11
+ const index = computed({
12
+ get: () => props.modelValue,
13
+ set: (v) => emit("update:modelValue", v)
14
+ });
15
+ const isFirst = computed(() => index.value === 0);
16
+ const isLast = computed(() => index.value === props.steps.length - 1);
17
+ const activeStep = computed(() => props.steps[index.value]);
18
+ function back() {
19
+ if (!isFirst.value) index.value--;
20
+ }
21
+ function next() {
22
+ if (!props.canAdvance || props.busy) return;
23
+ if (isLast.value) emit("finish");
24
+ else index.value++;
25
+ }
26
+ function goTo(i) {
27
+ if (i < index.value) index.value = i;
28
+ }
29
+ </script>
30
+
31
+ <template>
32
+ <div class="space-y-5">
33
+ <!-- Go-Live-style stepper: a fill bar + label per step, completed steps
34
+ are clickable to jump back. -->
35
+ <div class="flex gap-3">
36
+ <div
37
+ v-for="(s, i) in steps"
38
+ :key="s.key"
39
+ class="flex-1"
40
+ :class="i < index ? 'cursor-pointer' : ''"
41
+ @click="goTo(i)"
42
+ >
43
+ <div class="h-1 rounded-full transition-colors" :class="i <= index ? 'bg-primary' : 'bg-border'" />
44
+ <div class="mt-2 text-xs font-medium" :class="i <= index ? 'text-primary' : 'text-muted-foreground'">{{ t("shared.wizard.step.label", { number: i + 1 }) }}</div>
45
+ <div class="text-sm" :class="i <= index ? 'text-[#0B0522]' : 'text-muted-foreground'">{{ s.title }}</div>
46
+ </div>
47
+ </div>
48
+
49
+ <div>
50
+ <slot :step="activeStep" :index="index" />
51
+ </div>
52
+
53
+ <div class="flex items-center justify-between border-t pt-3">
54
+ <!-- Custom footer (e.g. multiple finish actions on a review step). Falls
55
+ back to the standard Back / Next·Finish pair. -->
56
+ <slot name="footer" :back="back" :next="next" :is-first="isFirst" :is-last="isLast" :can-advance="canAdvance" :busy="busy" :index="index">
57
+ <Button variant="ghost" :disabled="isFirst || busy" @click="back">
58
+ {{ t("shared.wizard.action.back") }}
59
+ </Button>
60
+ <Button :disabled="!canAdvance || busy" class="gap-1" @click="next">
61
+ {{ isLast ? t("shared.wizard.action.finish") : t("shared.wizard.action.next") }}
62
+ </Button>
63
+ </slot>
64
+ </div>
65
+ </div>
66
+ </template>
@@ -0,0 +1,47 @@
1
+ interface Step {
2
+ key: string;
3
+ title: string;
4
+ description?: string;
5
+ }
6
+ type __VLS_Props = {
7
+ steps: Step[];
8
+ modelValue?: number;
9
+ /** Gate the Next/Finish button for the current step. */
10
+ canAdvance?: boolean;
11
+ busy?: boolean;
12
+ };
13
+ declare var __VLS_1: {
14
+ step: any;
15
+ index: any;
16
+ }, __VLS_3: {
17
+ back: any;
18
+ next: any;
19
+ isFirst: any;
20
+ isLast: any;
21
+ canAdvance: any;
22
+ busy: any;
23
+ index: any;
24
+ };
25
+ type __VLS_Slots = {} & {
26
+ default?: (props: typeof __VLS_1) => any;
27
+ } & {
28
+ footer?: (props: typeof __VLS_3) => any;
29
+ };
30
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
31
+ "update:modelValue": (args_0: number) => any;
32
+ finish: () => any;
33
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
34
+ "onUpdate:modelValue"?: ((args_0: number) => any) | undefined;
35
+ onFinish?: (() => any) | undefined;
36
+ }>, {
37
+ modelValue: number;
38
+ canAdvance: boolean;
39
+ busy: boolean;
40
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
41
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
42
+ export default _default;
43
+ type __VLS_WithSlots<T, S> = T & {
44
+ new (): {
45
+ $slots: S;
46
+ };
47
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Open/closed state for a list surface's left rail, remembered per surface.
3
+ *
4
+ * The rail is a filter affordance, not content: an operator who collapsed it
5
+ * to give the table the full width expects it to stay collapsed on the next
6
+ * visit. Reads happen on mount (SSR-safe) and every change is written back;
7
+ * a browser without usable storage simply keeps the state for the session.
8
+ */
9
+ export declare function useListRail(key: string, initial?: boolean): any;
@@ -0,0 +1,19 @@
1
+ export function useListRail(key, initial = true) {
2
+ const storageKey = `commerce.listRail.${key}`;
3
+ const open = ref(initial);
4
+ onMounted(() => {
5
+ try {
6
+ const raw = window.localStorage.getItem(storageKey);
7
+ if (raw != null) open.value = raw === "1";
8
+ } catch {
9
+ }
10
+ });
11
+ watch(open, (v) => {
12
+ try {
13
+ if (typeof window === "undefined") return;
14
+ window.localStorage.setItem(storageKey, v ? "1" : "0");
15
+ } catch {
16
+ }
17
+ });
18
+ return open;
19
+ }
@@ -0,0 +1,41 @@
1
+ import type { Ref } from 'vue';
2
+ import type { StorageAsset, StorageFolder, StorageFolderNode, UploadProgress } from '../types/storage.js';
3
+ /**
4
+ * Media library provider seam — the storage client behind the shared media
5
+ * components (`MediaUploader`, `MediaPickerDialog`, `MediaFolderTree`).
6
+ *
7
+ * Same shape as the `usePlatformAuth` / `usePlatformTenant` contract: the
8
+ * components never import a host's storage client, they call this. The
9
+ * standalone default below is inert (no assets, uploads report as failed), so a
10
+ * module host that hasn't wired Storage still renders the dialogs instead of
11
+ * crashing. The full cockpit overrides it at `app/composables/useMediaLibrary.ts`
12
+ * — an app-level auto-import beats this module-registered one (priority -1).
13
+ */
14
+ export type { StorageAsset, StorageFolder, StorageFolderNode, UploadProgress };
15
+ export interface MediaLibraryClient {
16
+ /** Assets in the current view. */
17
+ assets: Ref<StorageAsset[]>;
18
+ /** Folders nested by `parent_id`, each level sorted by name. */
19
+ folderTree: Ref<StorageFolderNode[]>;
20
+ currentFolderId: Ref<string | null>;
21
+ /** 'all' = every asset; 'unfiled' = folder-less; 'folder' = `currentFolderId`. */
22
+ currentView: Ref<'all' | 'unfiled' | 'folder'>;
23
+ currentFolder: Ref<StorageFolder | null>;
24
+ loadingAssets: Ref<boolean>;
25
+ hasMore: Ref<boolean>;
26
+ /** Load folders + the current view's assets. */
27
+ init: () => Promise<void>;
28
+ openFolder: (folderId: string) => Promise<void>;
29
+ showAllMedia: () => Promise<void>;
30
+ loadMore: () => Promise<void>;
31
+ /** Upload a batch into `folderId`, reporting per-file progress. */
32
+ uploadMany: (files: File[], folderId?: string | null, onItem?: (index: number, progress: UploadProgress) => void) => Promise<{
33
+ created: number;
34
+ failed: number;
35
+ }>;
36
+ /** Displayable thumbnail URL, or null when the kind has no preview. */
37
+ thumbnailUrl: (asset: StorageAsset, size?: number) => string | null;
38
+ /** True while the asset is still being scanned/processed. */
39
+ isProcessing: (asset: StorageAsset) => boolean;
40
+ }
41
+ export declare function useMediaLibrary(): MediaLibraryClient;
@@ -0,0 +1,36 @@
1
+ import { computed, ref } from "vue";
2
+ export function useMediaLibrary() {
3
+ const assets = ref([]);
4
+ const folderTree = ref([]);
5
+ const currentFolderId = ref(null);
6
+ const currentView = ref("all");
7
+ const currentFolder = computed(() => null);
8
+ const loadingAssets = ref(false);
9
+ const hasMore = ref(false);
10
+ const noop = async () => {
11
+ };
12
+ return {
13
+ assets,
14
+ folderTree,
15
+ currentFolderId,
16
+ currentView,
17
+ currentFolder,
18
+ loadingAssets,
19
+ hasMore,
20
+ init: noop,
21
+ openFolder: noop,
22
+ showAllMedia: noop,
23
+ loadMore: noop,
24
+ async uploadMany(files, _folderId, onItem) {
25
+ files.forEach((file, i) => onItem?.(i, {
26
+ name: file.name,
27
+ pct: 0,
28
+ status: "failed",
29
+ error: useT()("shared.media.uploader.error.nostorage")
30
+ }));
31
+ return { created: 0, failed: files.length };
32
+ },
33
+ thumbnailUrl: () => null,
34
+ isProcessing: () => false
35
+ };
36
+ }
@@ -0,0 +1,35 @@
1
+ import type { Ref } from 'vue';
2
+ /**
3
+ * Platform auth provider — the seam that lets a studio surface run either
4
+ * inside the full cockpit or standalone.
5
+ *
6
+ * This is the **standalone default**: it sources a bearer token from runtime
7
+ * config (`NUXT_PUBLIC_DEV_TOKEN`) or `localStorage['console.token']`. The
8
+ * full cockpit ships an override at `app/composables/usePlatformAuth.ts`
9
+ * (app-level auto-imports take precedence over this module's) that returns the
10
+ * real Appwrite/Console-sourced operator token.
11
+ *
12
+ * Studio modules (e.g. `@revenexx/studio-integrations`) call this instead of
13
+ * `useConsoleAuth` directly, so they carry no cockpit-only dependency.
14
+ */
15
+ export interface PlatformAuth {
16
+ getToken: () => Promise<string | null>;
17
+ /**
18
+ * Whether a token is available NOW, as a reactive flag. OPTIONAL, because the
19
+ * standalone default below has nothing to report — a value in `localStorage` is
20
+ * either there or not, with no moment at which it becomes there.
21
+ *
22
+ * It exists for the hosts where `getToken()` legitimately answers null for a
23
+ * while and then starts working: the cockpit's override refreshes through
24
+ * `useConsoleAuth`, which backs off for 15s after a failed refresh and returns
25
+ * null for the duration. A consumer that only reacts to something ELSE changing
26
+ * — `usePlatformTalkback` watched the tenant slug — never retries and stays
27
+ * dead for the life of the tab.
28
+ *
29
+ * Deliberately a flag rather than the token itself: nothing outside the auth
30
+ * seam should hold a token it did not just ask for, and a stale one in a `ref`
31
+ * is exactly how that happens.
32
+ */
33
+ ready?: Readonly<Ref<boolean>>;
34
+ }
35
+ export declare function usePlatformAuth(): PlatformAuth;
@@ -0,0 +1,17 @@
1
+ export function usePlatformAuth() {
2
+ const config = useRuntimeConfig();
3
+ return {
4
+ async getToken() {
5
+ const fromConfig = config.public.devToken || null;
6
+ if (fromConfig) return fromConfig;
7
+ if (import.meta.client) {
8
+ try {
9
+ return localStorage.getItem("console.token");
10
+ } catch {
11
+ return null;
12
+ }
13
+ }
14
+ return null;
15
+ }
16
+ };
17
+ }
@@ -0,0 +1,29 @@
1
+ import type { ShallowRef } from 'vue';
2
+ import type { Talkback } from '@revenexx/talkback-js';
3
+ /**
4
+ * Platform realtime provider — the third seam beside `usePlatformAuth` and
5
+ * `usePlatformTenant`, and it composes both of them.
6
+ *
7
+ * A studio module never builds a Talkback client itself: it calls
8
+ * `useTalkbackTopic()` / `useTalkbackResource()` and this decides whether there
9
+ * is a connection behind them. One client for the whole application — a second
10
+ * `createTalkback` would open a second WebSocket and mint its own connection
11
+ * token.
12
+ *
13
+ * OFF IS A FIRST-CLASS STATE. An empty `talkbackHost` means no client is built,
14
+ * neither `@revenexx/talkback-js` nor `centrifuge` is loaded, and the channel
15
+ * composables become no-ops — so a call site needs no guard and a host that has
16
+ * no realtime stack is not half-connected. That is also the production gate: the
17
+ * edge has not been shown to pass a WebSocket upgrade yet (PE-382 T0.2).
18
+ */
19
+ export interface PlatformTalkback {
20
+ /**
21
+ * The client, or `null` while it is still loading or realtime is off.
22
+ * Reactive: the client arrives after a dynamic import, so a component that
23
+ * rendered first has to be able to pick it up.
24
+ */
25
+ talkback: ShallowRef<Talkback | null>;
26
+ /** Whether this host has realtime configured at all. */
27
+ enabled: boolean;
28
+ }
29
+ export declare function usePlatformTalkback(): PlatformTalkback;
@@ -0,0 +1,54 @@
1
+ import { watch } from "vue";
2
+ import { createPlatformTalkback, setTalkback, talkbackRef } from "../lib/talkback.js";
3
+ function withAppBase(baseURL, path) {
4
+ if (/^https?:\/\//.test(path)) return path;
5
+ const base = (baseURL || "/").replace(/\/+$/, "");
6
+ return `${base}/${path.replace(/^\/+/, "")}`;
7
+ }
8
+ let started = false;
9
+ export function usePlatformTalkback() {
10
+ const config = useRuntimeConfig();
11
+ const host = String(config.public.talkbackHost || "");
12
+ const enabled = Boolean(host);
13
+ if (enabled && import.meta.client && !started) {
14
+ started = true;
15
+ const { getToken, ready: authReady } = usePlatformAuth();
16
+ const { slug, load } = usePlatformTenant();
17
+ const baseURL = String(config.app?.baseURL || "/");
18
+ const facade = host.replace(/\/+$/, "");
19
+ const tokenEndpoint = withAppBase(baseURL, String(config.public.talkbackTokenEndpoint || `${facade}/v1/tokens`));
20
+ const subscriptionTokenEndpoint = withAppBase(baseURL, String(config.public.talkbackSubscriptionTokenEndpoint || `${facade}/v1/subscription-tokens`));
21
+ const start = (async () => {
22
+ const tb = await createPlatformTalkback({
23
+ host,
24
+ tokenEndpoint,
25
+ subscriptionTokenEndpoint,
26
+ tenant: () => slug.value ?? "",
27
+ getToken
28
+ });
29
+ setTalkback(tb);
30
+ let applied = null;
31
+ async function sync() {
32
+ if (!slug.value) {
33
+ try {
34
+ await load();
35
+ } catch {
36
+ }
37
+ }
38
+ const token = await getToken();
39
+ const next = slug.value && token ? slug.value : null;
40
+ if (next === applied) return;
41
+ tb.disconnect();
42
+ applied = next;
43
+ if (next) tb.connect();
44
+ }
45
+ const inputs = authReady ? [slug, authReady] : [slug];
46
+ watch(inputs, () => void sync(), { immediate: true });
47
+ })();
48
+ void start.catch((error) => {
49
+ if (!talkbackRef().value) started = false;
50
+ console.warn("[studio-shared] talkback failed to start", error);
51
+ });
52
+ }
53
+ return { talkback: talkbackRef(), enabled };
54
+ }
@@ -0,0 +1,14 @@
1
+ import type { Ref } from 'vue';
2
+ /**
3
+ * Platform tenant provider — companion to `usePlatformAuth`.
4
+ *
5
+ * Standalone default: the active tenant slug comes from runtime config
6
+ * (`NUXT_PUBLIC_DEV_TENANT`) or `localStorage['cockpit.devTenant']`. The full
7
+ * cockpit overrides this at `app/composables/usePlatformTenant.ts` to return
8
+ * the real active-tenant slug from `useConsoleTenants`.
9
+ */
10
+ export interface PlatformTenant {
11
+ slug: Ref<string | null>;
12
+ load: () => Promise<void>;
13
+ }
14
+ export declare function usePlatformTenant(): PlatformTenant;
@@ -0,0 +1,18 @@
1
+ import { computed, ref } from "vue";
2
+ export function usePlatformTenant() {
3
+ const config = useRuntimeConfig();
4
+ const stored = ref(null);
5
+ if (import.meta.client) {
6
+ try {
7
+ stored.value = localStorage.getItem("cockpit.devTenant");
8
+ } catch {
9
+ stored.value = null;
10
+ }
11
+ }
12
+ const slug = computed(() => config.public.devTenant || stored.value);
13
+ return {
14
+ slug,
15
+ async load() {
16
+ }
17
+ };
18
+ }
@@ -0,0 +1,11 @@
1
+ import type { ComputedRef } from 'vue';
2
+ import type { Translate } from '../lib/i18nBridge.js';
3
+ export declare function useT(): Translate;
4
+ /**
5
+ * The host's current UI locale (`'en'` when the host has no i18n).
6
+ *
7
+ * This is the INTERFACE language, not a content locale: it says which language
8
+ * the chrome is in, never which locale of a `labels` jsonb to show. For the
9
+ * latter see `useTenantLocales()` in `@revenexx/studio-commerce`.
10
+ */
11
+ export declare function useUiLocale(): ComputedRef<string>;
@@ -0,0 +1,8 @@
1
+ import { computed } from "vue";
2
+ import { currentLocale, translate } from "../lib/i18nBridge.js";
3
+ export function useT() {
4
+ return translate;
5
+ }
6
+ export function useUiLocale() {
7
+ return computed(() => currentLocale());
8
+ }
@@ -0,0 +1,78 @@
1
+ import type { ComputedRef } from 'vue';
2
+ import type { EnvelopeListener, RawListener, ResyncContext } from '@revenexx/talkback-js';
3
+ import type { MaybeRefOrGetter } from '@revenexx/talkback-js/vue';
4
+ /**
5
+ * The five channel composables, re-exported through this package so a studio
6
+ * module can subscribe with a bare name and carry no Talkback dependency of its
7
+ * own — the same contract `usePlatformAuth` / `usePlatformTenant` already have.
8
+ *
9
+ * THREE THINGS THESE WRAPPERS ADD, and each is a bug without them:
10
+ *
11
+ * 1. **The client is passed explicitly, never injected.** talkback-js's
12
+ * `useTalkback()` reads a module-PRIVATE `Symbol`, so two copies of the
13
+ * package are two different keys: it throws inside a studio module while the
14
+ * host looks healthy. Its `talkback` option exists for exactly this, and
15
+ * using it means no consumer has to know a plugin ran in the right order.
16
+ *
17
+ * 2. **They tolerate a client that does not exist yet.** The client arrives
18
+ * after a dynamic import, so any component that rendered first would
19
+ * otherwise subscribe to nothing and never retry. The delegate runs in a
20
+ * child `effectScope` that is created when the client appears and stopped
21
+ * with the caller's scope — which is also what gives the delegate's own
22
+ * `onScopeDispose(release)` a scope to bind to.
23
+ *
24
+ * 3. **`channel` is actually reactive.** Up to and including
25
+ * `@revenexx/talkback-js@0.1.3`, `ChannelSubscription.channel` is
26
+ * `{ value: null }` cast to a `Ref` — `ref` is never imported, so `isRef()`
27
+ * is false and a template binding never updates. Fixed upstream in
28
+ * talkback-js#11 but unreleased; this wrapper can be simplified to a
29
+ * pass-through once that ships.
30
+ *
31
+ * With realtime off (`talkbackHost` empty) every one of these is a no-op that
32
+ * reports a null channel, so a call site needs no `if (enabled)`.
33
+ */
34
+ /** Re-exported so a consumer types a ref-able argument without importing talkback-js. */
35
+ export type TalkbackMaybeRef<T> = MaybeRefOrGetter<T>;
36
+ export interface TalkbackChannelOptions {
37
+ /** One action, or several. Omit to receive every action on the channel. */
38
+ on?: string | readonly string[];
39
+ /** Called for each matching envelope. Events are refetch signals, not state. */
40
+ handler?: EnvelopeListener;
41
+ /** Every publication, envelope or not — how a `stream:` channel is read. */
42
+ raw?: RawListener;
43
+ /** The recovery buffer could not close the gap: refetch over HTTP. */
44
+ onResync?: (ctx: ResyncContext) => void;
45
+ /** Skip subscribing, e.g. while a route parameter is still empty. */
46
+ enabled?: TalkbackMaybeRef<boolean>;
47
+ }
48
+ export interface TalkbackChannel {
49
+ /** The currently subscribed channel, or null when nothing is subscribed. */
50
+ channel: ComputedRef<string | null>;
51
+ /** Releases early. Called automatically when the calling scope is disposed. */
52
+ stop: () => void;
53
+ }
54
+ /**
55
+ * The ACTION channel for a topic — a grid watching every instance of a kind.
56
+ *
57
+ * THE TOPIC INCLUDES THE ACTION, and one action is one channel (Centrifugo has
58
+ * no wildcards), so a grid watching three actions takes three calls:
59
+ *
60
+ * ```ts
61
+ * for (const action of ['started', 'finished', 'failed'] as const) {
62
+ * useTalkbackTopic(`revenexx.integrations.run.${action}`, { handler: () => load(true) })
63
+ * }
64
+ * ```
65
+ */
66
+ export declare function useTalkbackTopic(topic: TalkbackMaybeRef<string>, options?: TalkbackChannelOptions): TalkbackChannel;
67
+ /**
68
+ * The RESOURCE channel — one detail view, every action on it. The id may be a
69
+ * ref or a getter, and the subscription follows it, releasing the old channel
70
+ * before taking the new one.
71
+ */
72
+ export declare function useTalkbackResource(topic: TalkbackMaybeRef<string>, topicId: TalkbackMaybeRef<string | null | undefined>, options?: TalkbackChannelOptions): TalkbackChannel;
73
+ /** The signed-in user's own channel — notifications and toasts. */
74
+ export declare function useTalkbackUser(options?: TalkbackChannelOptions): TalkbackChannel;
75
+ /** A `stream:` channel. `onResync` fires on EVERY subscribe here — it keeps no history. */
76
+ export declare function useTalkbackStream(streamId: TalkbackMaybeRef<string | null | undefined>, options?: TalkbackChannelOptions): TalkbackChannel;
77
+ /** Who else is looking at this resource. */
78
+ export declare function useTalkbackPresence(vendor: TalkbackMaybeRef<string>, app: TalkbackMaybeRef<string>, entity: TalkbackMaybeRef<string>, id?: TalkbackMaybeRef<string | null | undefined>, options?: TalkbackChannelOptions): TalkbackChannel;
@@ -0,0 +1,52 @@
1
+ import { computed, effectScope, onScopeDispose, ref, shallowRef, watch } from "vue";
2
+ import { talkbackRef } from "../lib/talkback.js";
3
+ function useDelegate(subscribe) {
4
+ const client = talkbackRef();
5
+ const name = ref(null);
6
+ const scope = shallowRef(null);
7
+ function stop() {
8
+ scope.value?.stop();
9
+ scope.value = null;
10
+ name.value = null;
11
+ }
12
+ const unwatch = watch(client, (tb) => {
13
+ stop();
14
+ if (!tb) return;
15
+ const child = effectScope();
16
+ scope.value = child;
17
+ void (async () => {
18
+ const vue = await import("@revenexx/talkback-js/vue");
19
+ if (scope.value !== child) return;
20
+ child.run(() => {
21
+ let sub;
22
+ try {
23
+ sub = subscribe(vue, tb);
24
+ } catch {
25
+ name.value = null;
26
+ return;
27
+ }
28
+ watch(() => sub.channel.value, (v) => name.value = v, { immediate: true, flush: "sync" });
29
+ });
30
+ })();
31
+ }, { immediate: true });
32
+ onScopeDispose(() => {
33
+ unwatch();
34
+ stop();
35
+ });
36
+ return { channel: computed(() => name.value), stop };
37
+ }
38
+ export function useTalkbackTopic(topic, options = {}) {
39
+ return useDelegate((vue, tb) => vue.useTalkbackTopic(topic, { ...options, talkback: tb }));
40
+ }
41
+ export function useTalkbackResource(topic, topicId, options = {}) {
42
+ return useDelegate((vue, tb) => vue.useTalkbackResource(topic, topicId, { ...options, talkback: tb }));
43
+ }
44
+ export function useTalkbackUser(options = {}) {
45
+ return useDelegate((vue, tb) => vue.useTalkbackUser({ ...options, talkback: tb }));
46
+ }
47
+ export function useTalkbackStream(streamId, options = {}) {
48
+ return useDelegate((vue, tb) => vue.useTalkbackStream(streamId, { ...options, talkback: tb }));
49
+ }
50
+ export function useTalkbackPresence(vendor, app, entity, id = () => null, options = {}) {
51
+ return useDelegate((vue, tb) => vue.useTalkbackPresence(vendor, app, entity, id, { ...options, talkback: tb }));
52
+ }
@@ -0,0 +1,50 @@
1
+ import type { Ref } from 'vue';
2
+ /**
3
+ * What the host registers: both reactive, so a computed that formatted once
4
+ * re-runs when the operator changes the preference — no reload.
5
+ */
6
+ export interface FormatPreferenceSource {
7
+ /** Resolved BCP-47 tag, e.g. `de-AT`. Never `'auto'` — the host resolves. */
8
+ locale: Ref<string>;
9
+ /**
10
+ * `true` forces a 12-hour clock, `false` a 24-hour one, `undefined` leaves
11
+ * the choice to the locale's own convention (which is what `Intl` does when
12
+ * the option is absent).
13
+ */
14
+ hour12: Ref<boolean | undefined>;
15
+ }
16
+ /** Used when there is no host AND no i18n bridge. See the file comment. */
17
+ export declare const FALLBACK_FORMAT_LOCALE = "en-GB";
18
+ /**
19
+ * The region an `auto` preference resolves to for a UI language.
20
+ *
21
+ * Takes the language subtag, so `en-US` and `en` land on the same answer, and
22
+ * falls back to {@link FALLBACK_FORMAT_LOCALE} for a language we do not ship.
23
+ */
24
+ export declare function defaultRegionForLanguage(language: string | null | undefined): string;
25
+ /** Called once by the host. Pass `null` to detach (tests). */
26
+ export declare function setFormatPreferences(source: FormatPreferenceSource | null): void;
27
+ /** Whether a host preference is installed. Rarely needed — prefer the getters. */
28
+ export declare function hasFormatPreferences(): boolean;
29
+ /** The BCP-47 tag every package should format in. */
30
+ export declare function formatLocale(): string;
31
+ /** The operator's clock, or `undefined` to let the locale decide. */
32
+ export declare function hour12(): boolean | undefined;
33
+ /**
34
+ * A cached `DateTimeFormat` for an explicitly given locale and clock.
35
+ *
36
+ * The host uses this one: it has already resolved both from its own reactive
37
+ * state and must not have the answer re-derived from a registry it may not
38
+ * have populated yet. Packages want {@link dateTimeFormat}.
39
+ *
40
+ * `clock` is folded into the options rather than left to each call site, but an
41
+ * explicit `hour12` in `options` still wins — a cell that must print a 24-hour
42
+ * timestamp regardless (a log line, a cron preview) can say so.
43
+ */
44
+ export declare function dateTimeFormatFor(locale: string, options?: Intl.DateTimeFormatOptions, clock?: boolean): Intl.DateTimeFormat;
45
+ /** A cached `NumberFormat` for an explicitly given locale. */
46
+ export declare function numberFormatFor(locale: string, options?: Intl.NumberFormatOptions): Intl.NumberFormat;
47
+ /** A `DateTimeFormat` for whatever the host registered. What packages call. */
48
+ export declare function dateTimeFormat(options?: Intl.DateTimeFormatOptions): Intl.DateTimeFormat;
49
+ /** A `NumberFormat` for whatever the host registered. What packages call. */
50
+ export declare function numberFormat(options?: Intl.NumberFormatOptions): Intl.NumberFormat;
@@ -0,0 +1,54 @@
1
+ import { shallowRef } from "vue";
2
+ import { currentLocale } from "./i18nBridge.js";
3
+ export const FALLBACK_FORMAT_LOCALE = "en-GB";
4
+ const DEFAULT_REGION_BY_LANGUAGE = {
5
+ de: "de-DE",
6
+ en: "en-GB"
7
+ };
8
+ export function defaultRegionForLanguage(language) {
9
+ if (!language) return FALLBACK_FORMAT_LOCALE;
10
+ const subtag = String(language).split(/[-_]/)[0].toLowerCase();
11
+ return DEFAULT_REGION_BY_LANGUAGE[subtag] ?? FALLBACK_FORMAT_LOCALE;
12
+ }
13
+ const registered = shallowRef(null);
14
+ export function setFormatPreferences(source) {
15
+ registered.value = source;
16
+ }
17
+ export function hasFormatPreferences() {
18
+ return registered.value !== null;
19
+ }
20
+ export function formatLocale() {
21
+ const source = registered.value;
22
+ if (source) return source.locale.value;
23
+ return defaultRegionForLanguage(currentLocale());
24
+ }
25
+ export function hour12() {
26
+ return registered.value?.hour12.value;
27
+ }
28
+ const dateTimeFormats = /* @__PURE__ */ new Map();
29
+ const numberFormats = /* @__PURE__ */ new Map();
30
+ export function dateTimeFormatFor(locale, options = {}, clock) {
31
+ const resolved = clock === void 0 || "hour12" in options ? options : { ...options, hour12: clock };
32
+ const key = `${locale}|${JSON.stringify(resolved)}`;
33
+ let format = dateTimeFormats.get(key);
34
+ if (!format) {
35
+ format = new Intl.DateTimeFormat(locale, resolved);
36
+ dateTimeFormats.set(key, format);
37
+ }
38
+ return format;
39
+ }
40
+ export function numberFormatFor(locale, options = {}) {
41
+ const key = `${locale}|${JSON.stringify(options)}`;
42
+ let format = numberFormats.get(key);
43
+ if (!format) {
44
+ format = new Intl.NumberFormat(locale, options);
45
+ numberFormats.set(key, format);
46
+ }
47
+ return format;
48
+ }
49
+ export function dateTimeFormat(options = {}) {
50
+ return dateTimeFormatFor(formatLocale(), options, hour12());
51
+ }
52
+ export function numberFormat(options = {}) {
53
+ return numberFormatFor(formatLocale(), options);
54
+ }