-
@@ -83,36 +96,36 @@ function dotClass(st: KernelStatus): string {
-
+
-
+
Stop
-
+
Run All
-
+
-
+
{{ activeKernel?.name || 'Select Kernel' }}
@@ -158,7 +171,7 @@ function dotClass(st: KernelStatus): string {
-
+
@@ -173,8 +186,13 @@ function dotClass(st: KernelStatus): string {
-
- {{ statusLabel(kernelStatus) }}
+
+
+
{{ statusLabel(kernelStatus) }}
+
diff --git a/plugins/notebook/tests/unit/components/KernelStatusIndicator.test.ts b/plugins/notebook/tests/unit/components/KernelStatusIndicator.test.ts
index 2ae8db3..f28875f 100644
--- a/plugins/notebook/tests/unit/components/KernelStatusIndicator.test.ts
+++ b/plugins/notebook/tests/unit/components/KernelStatusIndicator.test.ts
@@ -11,6 +11,6 @@ describe('KernelStatusIndicator', () => {
it('shows kernel name when provided', () => {
render(KernelStatusIndicator, { props: { status: 'busy', kernelName: 'Pyodide' } })
expect(screen.getByText('Pyodide')).toBeInTheDocument()
- expect(screen.getByText('Running...')).toBeInTheDocument()
+ expect(screen.getByText('Running…')).toBeInTheDocument()
})
})
diff --git a/plugins/notebook/tests/unit/components/Notebook.test.ts b/plugins/notebook/tests/unit/components/Notebook.test.ts
index 732573b..2b37101 100644
--- a/plugins/notebook/tests/unit/components/Notebook.test.ts
+++ b/plugins/notebook/tests/unit/components/Notebook.test.ts
@@ -9,7 +9,7 @@ const data: NotebookData = { metadata: { title: 'T' }, cells: [] }
describe('Notebook', () => {
it('shows the empty state with add buttons', () => {
render(Notebook, { props: { initialData: data } })
- expect(screen.getByText('Add your first cell')).toBeInTheDocument()
+ expect(screen.getByText('Start your notebook')).toBeInTheDocument()
})
it('adds a cell when an empty-state button is clicked', async () => {
diff --git a/plugins/results-viewer/scripts/build-shinylive-export.R b/plugins/results-viewer/scripts/build-shinylive-export.R
index 8e36963..16a3174 100644
--- a/plugins/results-viewer/scripts/build-shinylive-export.R
+++ b/plugins/results-viewer/scripts/build-shinylive-export.R
@@ -43,18 +43,19 @@ if (!file.exists(duckdb_tgz) || file.info(duckdb_tgz)$size == 0) {
}
cat(" bundled", basename(duckdb_tgz), "(", file.info(duckdb_tgz)$size, "bytes)\n")
-cat("\nStep 1c: Version-tag shinylive's service worker (caching left OFF)\n")
-# Do NOT enable shinylive's `useCaching`. With it on, the SW caches /shinylive/
-# assets and the browser subsequently issues cache:"only-if-cached" requests;
-# the app_* proxy handler rebuilds those into a new Request that inherits a
-# non-same-origin mode, which throws ("only-if-cached can be set only with
-# same-origin mode"). The rejected fetch means the embedded app never registers
-# with the SW ("App URL not registered") and renders empty. Leaving useCaching
-# at its shipped default (false) keeps the proxy path working.
-# We still fold the app build id into the SW cache version so each rebuild
-# invalidates any stale registration cleanly. shinylive's export contains more
-# than one copy of shinylive-sw.js (the active one sits at the export root);
-# patch every copy.
+cat("\nStep 1c: Enable + version-tag shinylive's service worker cache\n")
+# Enable shinylive's `useCaching` so the SW serves the heavy /shinylive/ assets
+# (WebR runtime + R package binaries, ~150 MB) cache-first on repeat opens.
+# Two guards make this safe: Chromium issues cache:"only-if-cached" requests
+# whose mode is not "same-origin"; both fetch() and the Request constructor
+# reject that combination ("only-if-cached can be set only with same-origin
+# mode"), which killed asset loads and app_* proxying ("App URL not
+# registered") when caching was first tried. Such requests are (a) skipped in
+# the fetch handler so the browser resolves them natively from its HTTP cache,
+# and (b) normalized in the app_* proxy Request rebuild.
+# The app build id is folded into the SW cache version so each rebuild drops
+# stale caches cleanly. shinylive's export contains more than one copy of
+# shinylive-sw.js (the active one sits at the export root); patch every copy.
sw_files <- list.files(export_dir, pattern = "^shinylive-sw\\.js$",
recursive = TRUE, full.names = TRUE)
if (length(sw_files) == 0) {
@@ -65,10 +66,18 @@ patched_any <- FALSE
for (sw_path in sw_files) {
txt <- readChar(sw_path, file.info(sw_path)$size)
before <- txt
- # Force useCaching off even if a future shinylive ships it on.
- txt <- sub("var useCaching = true;", "var useCaching = false;", txt, fixed = TRUE)
+ txt <- sub("var useCaching = false;", "var useCaching = true;", txt, fixed = TRUE)
txt <- sub('var version = "v10";',
sprintf('var version = "v10-%s";', build_id), txt, fixed = TRUE)
+ txt <- sub("cache: request.cache,",
+ 'cache: request.cache === "only-if-cached" ? void 0 : request.cache,',
+ txt, fixed = TRUE)
+ txt <- sub("if (useCaching) {",
+ paste0('if (request.cache === "only-if-cached" && request.mode !== "same-origin") {\n',
+ " return;\n",
+ " }\n",
+ " if (useCaching) {"),
+ txt, fixed = TRUE)
if (!identical(txt, before)) {
writeLines(txt, sw_path, sep = "")
patched_any <- TRUE
@@ -78,7 +87,7 @@ for (sw_path in sw_files) {
if (!patched_any) {
stop("build-shinylive-export: useCaching/version markers not found in any shinylive-sw.js")
}
-cat(" version tagged", build_id, "(useCaching left off)\n")
+cat(" version tagged", build_id, "(useCaching ON with only-if-cached guards)\n")
cat("\nStep 2: Override Java-dep packages with pure-R shims\n")
temp_lib <- tempfile("shim-libs-")
diff --git a/plugins/results-viewer/src/components/LoadingOverlay.vue b/plugins/results-viewer/src/components/LoadingOverlay.vue
index 4ecbe65..78a8ff2 100644
--- a/plugins/results-viewer/src/components/LoadingOverlay.vue
+++ b/plugins/results-viewer/src/components/LoadingOverlay.vue
@@ -28,7 +28,7 @@ defineProps<{ message: string }>()
width: 48px;
height: 48px;
border: 4px solid #e0e0e0;
- border-top-color: #1976d2;
+ border-top-color: rgb(var(--v-theme-primary, 0, 0, 128));
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 24px;
diff --git a/plugins/results-viewer/src/components/ShinyFrame.vue b/plugins/results-viewer/src/components/ShinyFrame.vue
index 9fdb8b3..5bf177f 100644
--- a/plugins/results-viewer/src/components/ShinyFrame.vue
+++ b/plugins/results-viewer/src/components/ShinyFrame.vue
@@ -238,7 +238,7 @@ onBeforeUnmount(() => {
width: 48px;
height: 48px;
border: 4px solid #e5e7eb;
- border-top-color: #1976d2;
+ border-top-color: rgb(var(--v-theme-primary, 0, 0, 128));
border-radius: 50%;
animation: rv-spin 1s linear infinite;
}
diff --git a/plugins/strategus/src/StrategusApp.vue b/plugins/strategus/src/StrategusApp.vue
index 144e39a..0a45053 100644
--- a/plugins/strategus/src/StrategusApp.vue
+++ b/plugins/strategus/src/StrategusApp.vue
@@ -1,11 +1,14 @@
+
+
+
diff --git a/plugins/studies/src/components/StudiesSidebar.vue b/plugins/studies/src/components/StudiesSidebar.vue
index 30bd340..b433bb6 100644
--- a/plugins/studies/src/components/StudiesSidebar.vue
+++ b/plugins/studies/src/components/StudiesSidebar.vue
@@ -14,6 +14,19 @@
{{ it.label }}
+
+
+
+
@@ -31,6 +44,10 @@ const items: Array<{ key: StudiesSection; label: string; icon: string }> = [
{ key: 'network', label: 'Network', icon: 'mdi-earth' },
{ key: 'results', label: 'Results', icon: 'mdi-chart-box-outline' },
];
+
+const bottomItems: Array<{ key: StudiesSection; label: string; icon: string }> = [
+ { key: 'configuration', label: 'Configuration', icon: 'mdi-cog-outline' },
+];
diff --git a/plugins/studies/src/views/NetworkTab.vue b/plugins/studies/src/views/NetworkTab.vue
index f308d12..c480a15 100644
--- a/plugins/studies/src/views/NetworkTab.vue
+++ b/plugins/studies/src/views/NetworkTab.vue
@@ -1,7 +1,11 @@
diff --git a/plugins/studies/src/views/ResultsTab.vue b/plugins/studies/src/views/ResultsTab.vue
index 4794c14..6f6b074 100644
--- a/plugins/studies/src/views/ResultsTab.vue
+++ b/plugins/studies/src/views/ResultsTab.vue
@@ -1,27 +1,23 @@