@poveste/app 0.7.0 → 0.8.1
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.
- package/dist/bundled/app.css +6 -6
- package/dist/bundled/components/story/StoryVariantListItem.js +1 -1
- package/dist/bundled/components/story/StoryVariantListItem.vue_vue_type_script_setup_true_lang.js +29 -10
- package/dist/bundled/components/story/StoryVariantSinglePreviewRemote.vue_vue_type_script_setup_true_lang.js +40 -2
- package/dist/bundled/components/tree/StoryListItem.js +1 -1
- package/dist/bundled/components/tree/StoryListItem.vue_vue_type_script_setup_true_lang.js +24 -11
- package/dist/bundled/sandbox.js +9 -0
- package/dist/bundled/stores/story-errors.js +26 -0
- package/dist/style.css +28 -0
- package/package.json +4 -4
- package/src/app/components/story/StoryVariantListItem.vue +18 -1
- package/src/app/components/story/StoryVariantSinglePreviewRemote.vue +44 -0
- package/src/app/components/tree/StoryListItem.vue +15 -0
- package/src/app/sandbox.ts +12 -0
- package/src/app/stores/story-errors.ts +48 -0
package/dist/bundled/app.css
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
@scope (:root) to (.__poveste-render-story:not(.__poveste-render-custom-controls)) {
|
|
2
|
-
.bind-tree-margin[data-v-
|
|
3
|
-
margin-left: var(--
|
|
2
|
+
.bind-tree-margin[data-v-a603aebf] {
|
|
3
|
+
margin-left: var(--v18724f1f);
|
|
4
4
|
}
|
|
5
|
-
.bind-icon-color[data-v-
|
|
6
|
-
color: var(--
|
|
5
|
+
.bind-icon-color[data-v-a603aebf] {
|
|
6
|
+
color: var(--v45b473db);
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
@scope (:root) to (.__poveste-render-story:not(.__poveste-render-custom-controls)) {
|
|
@@ -129,8 +129,8 @@
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
@scope (:root) to (.__poveste-render-story:not(.__poveste-render-custom-controls)) {
|
|
132
|
-
.bind-icon-color[data-v-
|
|
133
|
-
color: var(--
|
|
132
|
+
.bind-icon-color[data-v-8112241f] {
|
|
133
|
+
color: var(--v29a7233a);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
@scope (:root) to (.__poveste-render-story:not(.__poveste-render-custom-controls)) {
|
|
@@ -2,6 +2,6 @@ import _plugin_vue_export_helper_default from "../../_virtual/_plugin-vue_export
|
|
|
2
2
|
import StoryVariantListItem_vue_vue_type_script_setup_true_lang_default from "./StoryVariantListItem.vue_vue_type_script_setup_true_lang.js";
|
|
3
3
|
/* empty css */
|
|
4
4
|
//#region src/app/components/story/StoryVariantListItem.vue
|
|
5
|
-
var StoryVariantListItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(StoryVariantListItem_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-
|
|
5
|
+
var StoryVariantListItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(StoryVariantListItem_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-8112241f"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { StoryVariantListItem_default as default };
|
package/dist/bundled/components/story/StoryVariantListItem.vue_vue_type_script_setup_true_lang.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { useStoryStore } from "../../stores/story.js";
|
|
2
|
+
import { useStoryErrorStore } from "../../stores/story-errors.js";
|
|
1
3
|
import { useScrollOnActive } from "../../util/scroll.js";
|
|
2
4
|
import BaseListItemLink_default from "../base/BaseListItemLink.js";
|
|
3
5
|
import { useCurrentVariantRoute } from "../../util/variant.js";
|
|
4
|
-
import { createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, toDisplayString, toRefs, unref, useCssVars, withCtx } from "@poveste/vendors/vue";
|
|
6
|
+
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, toDisplayString, toRefs, unref, useCssVars, withCtx } from "@poveste/vendors/vue";
|
|
5
7
|
import { Icon } from "@poveste/vendors/iconify";
|
|
6
8
|
//#region src/app/components/story/StoryVariantListItem.vue?vue&type=script&setup=true&lang.ts
|
|
7
9
|
var _hoisted_1 = { class: "truncate" };
|
|
@@ -12,11 +14,18 @@ var StoryVariantListItem_vue_vue_type_script_setup_true_lang_default = /*@__PURE
|
|
|
12
14
|
required: true
|
|
13
15
|
} },
|
|
14
16
|
setup(__props) {
|
|
15
|
-
useCssVars((_ctx) => ({ "
|
|
16
|
-
const
|
|
17
|
+
useCssVars((_ctx) => ({ "v29a7233a": unref(variant).iconColor }));
|
|
18
|
+
const props = __props;
|
|
19
|
+
const { variant } = toRefs(props);
|
|
17
20
|
const { isActive, targetRoute } = useCurrentVariantRoute(variant);
|
|
18
21
|
const el = ref();
|
|
19
22
|
useScrollOnActive(isActive, el);
|
|
23
|
+
const storyStore = useStoryStore();
|
|
24
|
+
const errorStore = useStoryErrorStore();
|
|
25
|
+
const error = computed(() => {
|
|
26
|
+
const storyId = storyStore.currentStory?.id;
|
|
27
|
+
return storyId ? errorStore.forVariant(storyId, props.variant.id) : void 0;
|
|
28
|
+
});
|
|
20
29
|
return (_ctx, _cache) => {
|
|
21
30
|
return openBlock(), createElementBlock("div", {
|
|
22
31
|
ref_key: "el",
|
|
@@ -28,13 +37,23 @@ var StoryVariantListItem_vue_vue_type_script_setup_true_lang_default = /*@__PURE
|
|
|
28
37
|
"is-active": unref(isActive),
|
|
29
38
|
class: "px-2 py-2 md:py-1.5 m-1 rounded-sm flex items-center gap-2"
|
|
30
39
|
}, {
|
|
31
|
-
default: withCtx(({ active }) => [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
default: withCtx(({ active }) => [
|
|
41
|
+
createVNode(unref(Icon), {
|
|
42
|
+
icon: unref(variant).icon ?? "carbon:cube",
|
|
43
|
+
class: normalizeClass(["w-5 h-5 sm:w-4 sm:h-4 flex-none", {
|
|
44
|
+
"text-gray-500": !active && !unref(variant).iconColor,
|
|
45
|
+
"bind-icon-color": !active && unref(variant).iconColor
|
|
46
|
+
}])
|
|
47
|
+
}, null, 8, ["icon", "class"]),
|
|
48
|
+
createElementVNode("span", _hoisted_1, toDisplayString(unref(variant).title), 1),
|
|
49
|
+
error.value ? (openBlock(), createBlock(unref(Icon), {
|
|
50
|
+
key: 0,
|
|
51
|
+
icon: "carbon:warning-alt",
|
|
52
|
+
class: "w-4 h-4 flex-none ml-auto text-red-500",
|
|
53
|
+
"data-testid": "variant-error-marker",
|
|
54
|
+
"aria-label": `Threw while rendering: ${error.value.message}`
|
|
55
|
+
}, null, 8, ["aria-label"])) : createCommentVNode("", true)
|
|
56
|
+
]),
|
|
38
57
|
_: 1
|
|
39
58
|
}, 8, ["to", "is-active"])], 512);
|
|
40
59
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EVENT_SEND, PREVIEW_SETTINGS_REQUEST, PREVIEW_SETTINGS_SYNC, SANDBOX_HEIGHT, SANDBOX_READY, SANDBOX_RETARGET, STATE_SYNC } from "../../util/const.js";
|
|
2
2
|
import { trackWindow } from "../../util/keyboard.js";
|
|
3
3
|
import { useStoryStore } from "../../stores/story.js";
|
|
4
|
+
import { useStoryErrorStore } from "../../stores/story-errors.js";
|
|
4
5
|
import { usePreviewSettingsStore } from "../../stores/preview-settings.js";
|
|
5
6
|
import { getSandboxUrl } from "../../util/sandbox.js";
|
|
6
7
|
import { createStateBridge } from "../../util/state-bridge.js";
|
|
@@ -9,9 +10,25 @@ import { useEventsStore } from "../../stores/events.js";
|
|
|
9
10
|
import { firstReportedHeight } from "../../util/grid-cell-height.js";
|
|
10
11
|
import StoryResponsivePreview_default from "./StoryResponsivePreview.js";
|
|
11
12
|
import { useEventListener } from "@poveste/vendors/vue-use";
|
|
12
|
-
import { computed, createBlock, createElementVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, ref, toRaw, watch, withCtx } from "@poveste/vendors/vue";
|
|
13
|
+
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, ref, toDisplayString, toRaw, unref, watch, withCtx } from "@poveste/vendors/vue";
|
|
14
|
+
import { Icon } from "@poveste/vendors/iconify";
|
|
15
|
+
import { SANDBOX_ERROR } from "@poveste/shared";
|
|
13
16
|
//#region src/app/components/story/StoryVariantSinglePreviewRemote.vue?vue&type=script&setup=true&lang.ts
|
|
14
17
|
var _hoisted_1 = ["src"];
|
|
18
|
+
var _hoisted_2 = {
|
|
19
|
+
key: 0,
|
|
20
|
+
class: "poveste-story-error absolute inset-0 overflow-auto p-6 bg-white dark:bg-gray-900",
|
|
21
|
+
"data-testid": "story-error"
|
|
22
|
+
};
|
|
23
|
+
var _hoisted_3 = { class: "flex items-center gap-2 text-red-600 dark:text-red-400 font-medium" };
|
|
24
|
+
var _hoisted_4 = {
|
|
25
|
+
class: "mt-2 font-mono text-sm text-red-700 dark:text-red-300",
|
|
26
|
+
"data-testid": "story-error-message"
|
|
27
|
+
};
|
|
28
|
+
var _hoisted_5 = {
|
|
29
|
+
key: 0,
|
|
30
|
+
class: "mt-4 text-xs whitespace-pre-wrap text-gray-600 dark:text-gray-400"
|
|
31
|
+
};
|
|
15
32
|
var MIN_HEIGHT = 40;
|
|
16
33
|
var MAX_AUTO_HEIGHT = 400;
|
|
17
34
|
var REUSE_LIMIT = 30;
|
|
@@ -30,6 +47,11 @@ var StoryVariantSinglePreviewRemote_vue_vue_type_script_setup_true_lang_default
|
|
|
30
47
|
setup(__props) {
|
|
31
48
|
const props = __props;
|
|
32
49
|
const settings = usePreviewSettingsStore().currentSettings;
|
|
50
|
+
const errorStore = useStoryErrorStore();
|
|
51
|
+
const storyError = computed(() => errorStore.forVariant(props.story.id, props.variant.id));
|
|
52
|
+
watch(() => [props.story.id, props.variant.id], () => {
|
|
53
|
+
errorStore.clear(props.story.id, props.variant.id);
|
|
54
|
+
});
|
|
33
55
|
const iframe = ref();
|
|
34
56
|
function postState(changes) {
|
|
35
57
|
iframe.value?.contentWindow?.postMessage({
|
|
@@ -67,6 +89,15 @@ var StoryVariantSinglePreviewRemote_vue_vue_type_script_setup_true_lang_default
|
|
|
67
89
|
if (!fromCurrentOccupant(event.data)) break;
|
|
68
90
|
setPreviewReady();
|
|
69
91
|
break;
|
|
92
|
+
case SANDBOX_ERROR:
|
|
93
|
+
if (!fromCurrentOccupant(event.data)) break;
|
|
94
|
+
errorStore.report({
|
|
95
|
+
message: event.data.message,
|
|
96
|
+
stack: event.data.stack,
|
|
97
|
+
storyId: props.story.id,
|
|
98
|
+
variantId: props.variant.id
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
70
101
|
case PREVIEW_SETTINGS_REQUEST:
|
|
71
102
|
syncSettings();
|
|
72
103
|
break;
|
|
@@ -192,7 +223,14 @@ var StoryVariantSinglePreviewRemote_vue_vue_type_script_setup_true_lang_default
|
|
|
192
223
|
style: normalizeStyle(previewStyle(isResponsiveEnabled, finalWidth, finalHeight)),
|
|
193
224
|
"data-testid": "preview-iframe",
|
|
194
225
|
onLoad: _cache[0] || (_cache[0] = ($event) => onIframeLoad())
|
|
195
|
-
}, null, 46, _hoisted_1)
|
|
226
|
+
}, null, 46, _hoisted_1), storyError.value ? (openBlock(), createElementBlock("div", _hoisted_2, [
|
|
227
|
+
createElementVNode("div", _hoisted_3, [createVNode(unref(Icon), {
|
|
228
|
+
icon: "carbon:warning-alt",
|
|
229
|
+
class: "w-5 h-5 flex-none"
|
|
230
|
+
}), _cache[1] || (_cache[1] = createElementVNode("span", null, "This component threw while rendering", -1))]),
|
|
231
|
+
createElementVNode("p", _hoisted_4, toDisplayString(storyError.value.message), 1),
|
|
232
|
+
storyError.value.stack ? (openBlock(), createElementBlock("pre", _hoisted_5, toDisplayString(storyError.value.stack), 1)) : createCommentVNode("", true)
|
|
233
|
+
])) : createCommentVNode("", true)]),
|
|
196
234
|
_: 1
|
|
197
235
|
}, 8, ["variant", "responsive"]);
|
|
198
236
|
};
|
|
@@ -2,6 +2,6 @@ import _plugin_vue_export_helper_default from "../../_virtual/_plugin-vue_export
|
|
|
2
2
|
import StoryListItem_vue_vue_type_script_setup_true_lang_default from "./StoryListItem.vue_vue_type_script_setup_true_lang.js";
|
|
3
3
|
/* empty css */
|
|
4
4
|
//#region src/app/components/tree/StoryListItem.vue
|
|
5
|
-
var StoryListItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(StoryListItem_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-
|
|
5
|
+
var StoryListItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(StoryListItem_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-a603aebf"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { StoryListItem_default as default };
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { useStoryErrorStore } from "../../stores/story-errors.js";
|
|
1
2
|
import { useScrollOnActive } from "../../util/scroll.js";
|
|
2
3
|
import BaseListItemLink_default from "../base/BaseListItemLink.js";
|
|
3
|
-
import { computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, toDisplayString, unref, useCssVars, withCtx } from "@poveste/vendors/vue";
|
|
4
|
+
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, toDisplayString, unref, useCssVars, withCtx } from "@poveste/vendors/vue";
|
|
4
5
|
import { Icon } from "@poveste/vendors/iconify";
|
|
5
6
|
import { useRoute } from "@poveste/vendors/vue-router";
|
|
6
7
|
//#region src/app/components/tree/StoryListItem.vue?vue&type=script&setup=true&lang.ts
|
|
7
8
|
var _hoisted_1 = { class: "bind-tree-margin flex items-center gap-2 pl-4 min-w-0" };
|
|
8
9
|
var _hoisted_2 = { class: "truncate" };
|
|
9
10
|
var _hoisted_3 = {
|
|
10
|
-
key:
|
|
11
|
+
key: 1,
|
|
11
12
|
class: "opacity-40 text-sm"
|
|
12
13
|
};
|
|
13
14
|
var StoryListItem_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
@@ -18,10 +19,12 @@ var StoryListItem_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
|
|
|
18
19
|
},
|
|
19
20
|
setup(__props) {
|
|
20
21
|
useCssVars((_ctx) => ({
|
|
21
|
-
"
|
|
22
|
-
"
|
|
22
|
+
"v18724f1f": filePadding.value,
|
|
23
|
+
"v45b473db": _ctx.story.iconColor
|
|
23
24
|
}));
|
|
24
25
|
const props = __props;
|
|
26
|
+
const errorStore = useStoryErrorStore();
|
|
27
|
+
const hasError = computed(() => errorStore.storyHasError(props.story.id));
|
|
25
28
|
const filePadding = computed(() => {
|
|
26
29
|
return `${props.depth * 12}px`;
|
|
27
30
|
});
|
|
@@ -42,13 +45,23 @@ var StoryListItem_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
|
|
|
42
45
|
},
|
|
43
46
|
class: "pl-0.5 pr-2 py-2 md:py-1.5 mx-1 rounded-sm"
|
|
44
47
|
}, {
|
|
45
|
-
default: withCtx(({ active }) => [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
default: withCtx(({ active }) => [
|
|
49
|
+
createElementVNode("span", _hoisted_1, [createVNode(unref(Icon), {
|
|
50
|
+
icon: __props.story.icon ?? "carbon:cube",
|
|
51
|
+
class: normalizeClass(["w-5 h-5 sm:w-4 sm:h-4 flex-none", {
|
|
52
|
+
"text-primary-500": !active && !__props.story.iconColor,
|
|
53
|
+
"bind-icon-color": !active && __props.story.iconColor
|
|
54
|
+
}])
|
|
55
|
+
}, null, 8, ["icon", "class"]), createElementVNode("span", _hoisted_2, toDisplayString(__props.story.title), 1)]),
|
|
56
|
+
hasError.value ? (openBlock(), createBlock(unref(Icon), {
|
|
57
|
+
key: 0,
|
|
58
|
+
icon: "carbon:warning-alt",
|
|
59
|
+
class: "w-4 h-4 flex-none ml-auto text-red-500",
|
|
60
|
+
"data-testid": "story-error-marker",
|
|
61
|
+
"aria-label": "A variant of this story threw while rendering"
|
|
62
|
+
})) : createCommentVNode("", true),
|
|
63
|
+
!__props.story.docsOnly ? (openBlock(), createElementBlock("span", _hoisted_3, toDisplayString(__props.story.variants.length), 1)) : createCommentVNode("", true)
|
|
64
|
+
]),
|
|
52
65
|
_: 1
|
|
53
66
|
}, 8, ["to"])], 512);
|
|
54
67
|
};
|
package/dist/bundled/sandbox.js
CHANGED
|
@@ -12,6 +12,7 @@ import { computed, createApp, h, onMounted, ref, watch } from "@poveste/vendors/
|
|
|
12
12
|
import { createPinia } from "@poveste/vendors/pinia";
|
|
13
13
|
import { files, onUpdate } from "virtual:$poveste-stories";
|
|
14
14
|
import { parseQuery } from "@poveste/vendors/vue-router";
|
|
15
|
+
import { reportStoryError } from "@poveste/shared";
|
|
15
16
|
//#region src/app/sandbox.ts
|
|
16
17
|
var query = parseQuery(window.location.search);
|
|
17
18
|
var storyId = ref(typeof query.storyId === "string" ? query.storyId : null);
|
|
@@ -29,6 +30,14 @@ onUpdate((newFiles) => {
|
|
|
29
30
|
var storedSettings = loadStoredPreviewSettings();
|
|
30
31
|
if (storedSettings) applyPreviewSettings(storedSettings);
|
|
31
32
|
if (window.parent && window.parent !== window) window.parent.postMessage({ type: PREVIEW_SETTINGS_REQUEST }, window.location.origin);
|
|
33
|
+
function reportUncaught(error) {
|
|
34
|
+
reportStoryError(error, {
|
|
35
|
+
storyId: storyId.value,
|
|
36
|
+
variantId: variantId.value
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
window.addEventListener("error", (event) => reportUncaught(event.error ?? event.message));
|
|
40
|
+
window.addEventListener("unhandledrejection", (event) => reportUncaught(event.reason));
|
|
32
41
|
var prefersDark = usePreferredDark();
|
|
33
42
|
var previewDark = computed(() => resolvePreviewDark(receivedSettings.colorScheme, prefersDark.value, isDark.value));
|
|
34
43
|
var darkClassTargets = [document.documentElement, document.querySelector("#app")];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { computed, ref } from "@poveste/vendors/vue";
|
|
2
|
+
import { defineStore } from "@poveste/vendors/pinia";
|
|
3
|
+
//#region src/app/stores/story-errors.ts
|
|
4
|
+
var useStoryErrorStore = defineStore("story-errors", () => {
|
|
5
|
+
const errors = ref(/* @__PURE__ */ new Map());
|
|
6
|
+
const key = (storyId, variantId) => `${storyId}::${variantId ?? ""}`;
|
|
7
|
+
function report(error) {
|
|
8
|
+
if (!error.storyId) return;
|
|
9
|
+
errors.value.set(key(error.storyId, error.variantId), error);
|
|
10
|
+
}
|
|
11
|
+
function clear(storyId, variantId) {
|
|
12
|
+
errors.value.delete(key(storyId, variantId));
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
errors,
|
|
16
|
+
report,
|
|
17
|
+
clear,
|
|
18
|
+
forVariant: computed(() => (storyId, variantId) => errors.value.get(key(storyId, variantId))),
|
|
19
|
+
storyHasError: computed(() => (storyId) => {
|
|
20
|
+
for (const [entry] of errors.value) if (entry.startsWith(`${storyId}::`)) return true;
|
|
21
|
+
return false;
|
|
22
|
+
})
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
//#endregion
|
|
26
|
+
export { useStoryErrorStore };
|
package/dist/style.css
CHANGED
|
@@ -335,7 +335,11 @@
|
|
|
335
335
|
:scope, :host {
|
|
336
336
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
|
337
337
|
monospace;
|
|
338
|
+
--color-red-300: oklch(80.8% 0.114 19.571);
|
|
339
|
+
--color-red-400: oklch(70.4% 0.191 22.216);
|
|
338
340
|
--color-red-500: oklch(63.7% 0.237 25.331);
|
|
341
|
+
--color-red-600: oklch(57.7% 0.245 27.325);
|
|
342
|
+
--color-red-700: oklch(50.5% 0.213 27.518);
|
|
339
343
|
--color-orange-500: oklch(70.5% 0.213 47.604);
|
|
340
344
|
--color-emerald-50: oklch(97.9% 0.021 166.113);
|
|
341
345
|
--color-emerald-100: oklch(95% 0.052 163.051);
|
|
@@ -990,6 +994,9 @@
|
|
|
990
994
|
.mt-2 {
|
|
991
995
|
margin-top: calc(var(--spacing) * 2);
|
|
992
996
|
}
|
|
997
|
+
.mt-4 {
|
|
998
|
+
margin-top: calc(var(--spacing) * 4);
|
|
999
|
+
}
|
|
993
1000
|
.mr-2 {
|
|
994
1001
|
margin-right: calc(var(--spacing) * 2);
|
|
995
1002
|
}
|
|
@@ -1669,6 +1676,9 @@
|
|
|
1669
1676
|
.p-4 {
|
|
1670
1677
|
padding: calc(var(--spacing) * 4);
|
|
1671
1678
|
}
|
|
1679
|
+
.p-6 {
|
|
1680
|
+
padding: calc(var(--spacing) * 6);
|
|
1681
|
+
}
|
|
1672
1682
|
.p-8 {
|
|
1673
1683
|
padding: calc(var(--spacing) * 8);
|
|
1674
1684
|
}
|
|
@@ -1812,6 +1822,9 @@
|
|
|
1812
1822
|
.whitespace-nowrap {
|
|
1813
1823
|
white-space: nowrap;
|
|
1814
1824
|
}
|
|
1825
|
+
.whitespace-pre-wrap {
|
|
1826
|
+
white-space: pre-wrap;
|
|
1827
|
+
}
|
|
1815
1828
|
.text-black\/\[1\%\] {
|
|
1816
1829
|
color: color-mix(in srgb, #000 1%, transparent);
|
|
1817
1830
|
@supports (color: color-mix(in lab, red, red)) {
|
|
@@ -1830,6 +1843,9 @@
|
|
|
1830
1843
|
color: color-mix(in oklab, var(--color-gray-500) 20%, transparent);
|
|
1831
1844
|
}
|
|
1832
1845
|
}
|
|
1846
|
+
.text-gray-600 {
|
|
1847
|
+
color: var(--color-gray-600);
|
|
1848
|
+
}
|
|
1833
1849
|
.text-gray-700 {
|
|
1834
1850
|
color: var(--color-gray-700);
|
|
1835
1851
|
}
|
|
@@ -1854,6 +1870,12 @@
|
|
|
1854
1870
|
.text-red-500 {
|
|
1855
1871
|
color: var(--color-red-500);
|
|
1856
1872
|
}
|
|
1873
|
+
.text-red-600 {
|
|
1874
|
+
color: var(--color-red-600);
|
|
1875
|
+
}
|
|
1876
|
+
.text-red-700 {
|
|
1877
|
+
color: var(--color-red-700);
|
|
1878
|
+
}
|
|
1857
1879
|
.text-white {
|
|
1858
1880
|
color: var(--color-white);
|
|
1859
1881
|
}
|
|
@@ -2307,6 +2329,12 @@
|
|
|
2307
2329
|
.dark\:text-primary-400:where(.ptw-dark, .ptw-dark *) {
|
|
2308
2330
|
color: var(--color-primary-400);
|
|
2309
2331
|
}
|
|
2332
|
+
.dark\:text-red-300:where(.ptw-dark, .ptw-dark *) {
|
|
2333
|
+
color: var(--color-red-300);
|
|
2334
|
+
}
|
|
2335
|
+
.dark\:text-red-400:where(.ptw-dark, .ptw-dark *) {
|
|
2336
|
+
color: var(--color-red-400);
|
|
2337
|
+
}
|
|
2310
2338
|
.dark\:text-white\/\[1\%\]:where(.ptw-dark, .ptw-dark *) {
|
|
2311
2339
|
color: color-mix(in srgb, #fff 1%, transparent);
|
|
2312
2340
|
@supports (color: color-mix(in lab, red, red)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poveste/app",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.1",
|
|
5
5
|
"description": "Pre-bundled UI for Poveste",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Sorin Gitlan"
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"@fontsource/noto-sans-display": "^5.3.0",
|
|
41
41
|
"fuse.js": "^7.0.0",
|
|
42
42
|
"shiki": "^3.0.0",
|
|
43
|
-
"@poveste/controls": "^0.
|
|
44
|
-
"@poveste/
|
|
45
|
-
"@poveste/
|
|
43
|
+
"@poveste/controls": "^0.8.1",
|
|
44
|
+
"@poveste/shared": "^0.8.1",
|
|
45
|
+
"@poveste/vendors": "^0.8.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@iconify-json/carbon": "^1.2.25",
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import type { PropType } from 'vue'
|
|
3
3
|
import type { Variant } from '../../types'
|
|
4
4
|
import { Icon } from '@iconify/vue'
|
|
5
|
-
import { ref, toRefs } from 'vue'
|
|
5
|
+
import { computed, ref, toRefs } from 'vue'
|
|
6
|
+
import { useStoryStore } from '../../stores/story'
|
|
7
|
+
import { useStoryErrorStore } from '../../stores/story-errors'
|
|
6
8
|
import { useScrollOnActive } from '../../util/scroll'
|
|
7
9
|
import { useCurrentVariantRoute } from '../../util/variant'
|
|
8
10
|
import BaseListItemLink from '../base/BaseListItemLink.vue'
|
|
@@ -18,6 +20,14 @@ const { variant } = toRefs(props)
|
|
|
18
20
|
const { isActive, targetRoute } = useCurrentVariantRoute(variant)
|
|
19
21
|
const el = ref<HTMLDivElement>()
|
|
20
22
|
useScrollOnActive(isActive, el)
|
|
23
|
+
|
|
24
|
+
// This list only ever shows the current story's variants.
|
|
25
|
+
const storyStore = useStoryStore()
|
|
26
|
+
const errorStore = useStoryErrorStore()
|
|
27
|
+
const error = computed(() => {
|
|
28
|
+
const storyId = storyStore.currentStory?.id
|
|
29
|
+
return storyId ? errorStore.forVariant(storyId, props.variant.id) : undefined
|
|
30
|
+
})
|
|
21
31
|
</script>
|
|
22
32
|
|
|
23
33
|
<template>
|
|
@@ -41,6 +51,13 @@ useScrollOnActive(isActive, el)
|
|
|
41
51
|
}"
|
|
42
52
|
/>
|
|
43
53
|
<span class="truncate">{{ variant.title }}</span>
|
|
54
|
+
<Icon
|
|
55
|
+
v-if="error"
|
|
56
|
+
icon="carbon:warning-alt"
|
|
57
|
+
class="w-4 h-4 flex-none ml-auto text-red-500"
|
|
58
|
+
data-testid="variant-error-marker"
|
|
59
|
+
:aria-label="`Threw while rendering: ${error.message}`"
|
|
60
|
+
/>
|
|
44
61
|
</BaseListItemLink>
|
|
45
62
|
</div>
|
|
46
63
|
</template>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import type { HstEvent } from '../../stores/events'
|
|
3
3
|
import type { Story, Variant } from '../../types'
|
|
4
|
+
import { Icon } from '@iconify/vue'
|
|
5
|
+
import { SANDBOX_ERROR } from '@poveste/shared'
|
|
4
6
|
import { useEventListener } from '@vueuse/core'
|
|
5
7
|
import { computed, onBeforeUnmount, ref, toRaw, watch } from 'vue'
|
|
6
8
|
import { useEventsStore } from '../../stores/events'
|
|
7
9
|
import { usePreviewSettingsStore } from '../../stores/preview-settings'
|
|
8
10
|
import { useStoryStore } from '../../stores/story'
|
|
11
|
+
import { useStoryErrorStore } from '../../stores/story-errors'
|
|
9
12
|
import { EVENT_SEND, PREVIEW_SETTINGS_REQUEST, PREVIEW_SETTINGS_SYNC, SANDBOX_HEIGHT, SANDBOX_READY, SANDBOX_RETARGET, STATE_SYNC } from '../../util/const'
|
|
10
13
|
import { firstReportedHeight } from '../../util/grid-cell-height'
|
|
11
14
|
import { trackWindow } from '../../util/keyboard'
|
|
@@ -24,6 +27,15 @@ const props = withDefaults(defineProps<{
|
|
|
24
27
|
})
|
|
25
28
|
|
|
26
29
|
const settings = usePreviewSettingsStore().currentSettings
|
|
30
|
+
const errorStore = useStoryErrorStore()
|
|
31
|
+
|
|
32
|
+
const storyError = computed(() => errorStore.forVariant(props.story.id, props.variant.id))
|
|
33
|
+
|
|
34
|
+
// The realm re-renders on every retarget, so a stale error must not outlive the
|
|
35
|
+
// render that produced it — otherwise a fixed story stays marked until reload.
|
|
36
|
+
watch(() => [props.story.id, props.variant.id], () => {
|
|
37
|
+
errorStore.clear(props.story.id, props.variant.id)
|
|
38
|
+
})
|
|
27
39
|
|
|
28
40
|
// Iframe
|
|
29
41
|
|
|
@@ -104,6 +116,15 @@ useEventListener(window, 'message', (event) => {
|
|
|
104
116
|
if (!fromCurrentOccupant(event.data)) break
|
|
105
117
|
setPreviewReady()
|
|
106
118
|
break
|
|
119
|
+
case SANDBOX_ERROR:
|
|
120
|
+
if (!fromCurrentOccupant(event.data)) break
|
|
121
|
+
errorStore.report({
|
|
122
|
+
message: event.data.message,
|
|
123
|
+
stack: event.data.stack,
|
|
124
|
+
storyId: props.story.id,
|
|
125
|
+
variantId: props.variant.id,
|
|
126
|
+
})
|
|
127
|
+
break
|
|
107
128
|
case PREVIEW_SETTINGS_REQUEST:
|
|
108
129
|
syncSettings()
|
|
109
130
|
break
|
|
@@ -303,5 +324,28 @@ function onIframeLoad() {
|
|
|
303
324
|
data-testid="preview-iframe"
|
|
304
325
|
@load="onIframeLoad()"
|
|
305
326
|
/>
|
|
327
|
+
|
|
328
|
+
<!--
|
|
329
|
+
Covers the preview rather than sitting beside it: a component that threw
|
|
330
|
+
has rendered something, but nothing about it can be trusted — the template
|
|
331
|
+
below is exactly what makes a broken story look like a working one (#323).
|
|
332
|
+
-->
|
|
333
|
+
<div
|
|
334
|
+
v-if="storyError"
|
|
335
|
+
class="poveste-story-error absolute inset-0 overflow-auto p-6 bg-white dark:bg-gray-900"
|
|
336
|
+
data-testid="story-error"
|
|
337
|
+
>
|
|
338
|
+
<div class="flex items-center gap-2 text-red-600 dark:text-red-400 font-medium">
|
|
339
|
+
<Icon icon="carbon:warning-alt" class="w-5 h-5 flex-none" />
|
|
340
|
+
<span>This component threw while rendering</span>
|
|
341
|
+
</div>
|
|
342
|
+
<p class="mt-2 font-mono text-sm text-red-700 dark:text-red-300" data-testid="story-error-message">
|
|
343
|
+
{{ storyError.message }}
|
|
344
|
+
</p>
|
|
345
|
+
<pre
|
|
346
|
+
v-if="storyError.stack"
|
|
347
|
+
class="mt-4 text-xs whitespace-pre-wrap text-gray-600 dark:text-gray-400"
|
|
348
|
+
>{{ storyError.stack }}</pre>
|
|
349
|
+
</div>
|
|
306
350
|
</StoryResponsivePreview>
|
|
307
351
|
</template>
|
|
@@ -3,6 +3,7 @@ import type { Story } from '../../types'
|
|
|
3
3
|
import { Icon } from '@iconify/vue'
|
|
4
4
|
import { computed, ref } from 'vue'
|
|
5
5
|
import { useRoute } from 'vue-router'
|
|
6
|
+
import { useStoryErrorStore } from '../../stores/story-errors'
|
|
6
7
|
import { useScrollOnActive } from '../../util/scroll'
|
|
7
8
|
import BaseListItemLink from '../base/BaseListItemLink.vue'
|
|
8
9
|
|
|
@@ -13,6 +14,12 @@ const props = withDefaults(defineProps<{
|
|
|
13
14
|
depth: 0,
|
|
14
15
|
})
|
|
15
16
|
|
|
17
|
+
// Only ever means "a variant of this story was seen to throw". A variant that
|
|
18
|
+
// has not rendered cannot have reported, so no marker is not a clean bill of
|
|
19
|
+
// health — see the store, and the open half of #323.
|
|
20
|
+
const errorStore = useStoryErrorStore()
|
|
21
|
+
const hasError = computed(() => errorStore.storyHasError(props.story.id))
|
|
22
|
+
|
|
16
23
|
const filePadding = computed(() => {
|
|
17
24
|
return `${props.depth * 12}px`
|
|
18
25
|
})
|
|
@@ -51,6 +58,14 @@ useScrollOnActive(isActive, el)
|
|
|
51
58
|
<span class="truncate">{{ story.title }}</span>
|
|
52
59
|
</span>
|
|
53
60
|
|
|
61
|
+
<Icon
|
|
62
|
+
v-if="hasError"
|
|
63
|
+
icon="carbon:warning-alt"
|
|
64
|
+
class="w-4 h-4 flex-none ml-auto text-red-500"
|
|
65
|
+
data-testid="story-error-marker"
|
|
66
|
+
aria-label="A variant of this story threw while rendering"
|
|
67
|
+
/>
|
|
68
|
+
|
|
54
69
|
<span
|
|
55
70
|
v-if="!story.docsOnly"
|
|
56
71
|
class="opacity-40 text-sm"
|
package/src/app/sandbox.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { StoryFile } from './types'
|
|
2
|
+
import { reportStoryError } from '@poveste/shared'
|
|
2
3
|
import { usePreferredDark } from '@vueuse/core'
|
|
3
4
|
import { createPinia } from 'pinia'
|
|
4
5
|
import { files, onUpdate } from 'virtual:$poveste-stories'
|
|
@@ -56,6 +57,17 @@ if (window.parent && window.parent !== window) {
|
|
|
56
57
|
window.parent.postMessage({ type: PREVIEW_SETTINGS_REQUEST }, window.location.origin)
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
// The framework hooks catch what the framework swallows; this catches what
|
|
61
|
+
// escapes it — a throw from an event handler, a rejected promise, an async
|
|
62
|
+
// effect. Neither alone is enough: Vue never lets a render error reach the
|
|
63
|
+
// window, and no framework hook sees an error raised outside its own call
|
|
64
|
+
// stack (#323).
|
|
65
|
+
function reportUncaught(error: unknown) {
|
|
66
|
+
reportStoryError(error, { storyId: storyId.value, variantId: variantId.value })
|
|
67
|
+
}
|
|
68
|
+
window.addEventListener('error', event => reportUncaught(event.error ?? event.message))
|
|
69
|
+
window.addEventListener('unhandledrejection', event => reportUncaught(event.reason))
|
|
70
|
+
|
|
59
71
|
// The story preview has its own color scheme, independent from the app chrome.
|
|
60
72
|
// The fallback covers settings stored before the option existed.
|
|
61
73
|
const prefersDark = usePreferredDark()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { StoryError } from '@poveste/shared'
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
// Errors reported by a sandbox while rendering a variant (#323).
|
|
6
|
+
//
|
|
7
|
+
// This only knows about variants that have actually rendered. A variant is not
|
|
8
|
+
// mounted until it scrolls into the grid's window (#103), so the absence of a
|
|
9
|
+
// marker means "not seen to fail", never "fine" — telling a book it is healthy
|
|
10
|
+
// would need a rendering pass, which is the open half of #323.
|
|
11
|
+
export const useStoryErrorStore = defineStore('story-errors', () => {
|
|
12
|
+
const errors = ref(new Map<string, StoryError>())
|
|
13
|
+
|
|
14
|
+
const key = (storyId: string, variantId?: string | null) => `${storyId}::${variantId ?? ''}`
|
|
15
|
+
|
|
16
|
+
function report(error: StoryError) {
|
|
17
|
+
if (!error.storyId) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
errors.value.set(key(error.storyId, error.variantId), error)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// A variant is re-rendered on every retarget, so last render wins: a story
|
|
24
|
+
// that has been fixed must stop being marked without a reload.
|
|
25
|
+
function clear(storyId: string, variantId?: string | null) {
|
|
26
|
+
errors.value.delete(key(storyId, variantId))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const forVariant = computed(() => (storyId: string, variantId?: string | null) =>
|
|
30
|
+
errors.value.get(key(storyId, variantId)))
|
|
31
|
+
|
|
32
|
+
const storyHasError = computed(() => (storyId: string) => {
|
|
33
|
+
for (const [entry] of errors.value) {
|
|
34
|
+
if (entry.startsWith(`${storyId}::`)) {
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
errors,
|
|
43
|
+
report,
|
|
44
|
+
clear,
|
|
45
|
+
forVariant,
|
|
46
|
+
storyHasError,
|
|
47
|
+
}
|
|
48
|
+
})
|