From ff36b950141758740e4da0745a6a591e83fe679a Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Mon, 27 Apr 2026 14:28:17 -0400
Subject: [PATCH 1/2] local history
---
frontend/src/index.js | 194 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 192 insertions(+), 2 deletions(-)
diff --git a/frontend/src/index.js b/frontend/src/index.js
index 7ce28ea7..b82ff118 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -17,6 +17,104 @@ const { useToast } = require('vue-toastification');
const appendCSS = require('./appendCSS');
appendCSS(require('vue-toastification/dist/index.css'));
+const RECENT_PAGES_STORAGE_KEY = 'studio:recent-pages-history';
+const MAX_RECENT_PAGES = 10;
+
+function formatHistoryLabel(route) {
+ if (!route || typeof route.path !== 'string') {
+ return 'Unknown page';
+ }
+ if (route.name === 'root') {
+ return 'Home';
+ }
+ if (route.name === 'model') {
+ return route.params?.model ? `Model: ${route.params.model}` : 'Model';
+ }
+ if (route.name === 'document') {
+ const model = route.params?.model ? `${route.params.model} ` : '';
+ const documentId = route.params?.documentId ? String(route.params.documentId) : '';
+ const shortDocumentId = documentId ? documentId.slice(0, 8) : '';
+ return `Document: ${model}${shortDocumentId}`.trim();
+ }
+ if (route.name === 'dashboard') {
+ return route.params?.dashboardId ? `Dashboard: ${route.params.dashboardId}` : 'Dashboard';
+ }
+ if (route.name === 'dashboards') {
+ return 'Dashboards';
+ }
+ if (route.name === 'tasks') {
+ return 'Tasks';
+ }
+ if (route.name === 'taskByName') {
+ return route.params?.name ? `Task: ${route.params.name}` : 'Task';
+ }
+ if (route.name === 'taskSingle') {
+ const taskName = route.params?.name ? `${route.params.name} ` : '';
+ const taskId = route.params?.id ? String(route.params.id) : '';
+ return `Task: ${taskName}${taskId}`.trim();
+ }
+ if (route.name === 'team') {
+ return 'Team';
+ }
+ if (route.name === 'chat' || route.name === 'chat index') {
+ return route.params?.threadId ? `Chat: ${route.params.threadId}` : 'Chat';
+ }
+ const normalizedPath = route.path.replace(/^\//, '');
+ return normalizedPath || 'Home';
+}
+
+function safeReadRecentPages() {
+ if (typeof window === 'undefined' || !window.localStorage) {
+ return [];
+ }
+ try {
+ const raw = window.localStorage.getItem(RECENT_PAGES_STORAGE_KEY);
+ if (!raw) {
+ return [];
+ }
+ const parsed = JSON.parse(raw);
+ if (!Array.isArray(parsed)) {
+ return [];
+ }
+ return parsed.filter(entry =>
+ entry &&
+ typeof entry.path === 'string' &&
+ typeof entry.label === 'string' &&
+ typeof entry.visitedAt === 'number'
+ ).slice(0, MAX_RECENT_PAGES);
+ } catch (err) {
+ return [];
+ }
+}
+
+function saveRecentPages(entries) {
+ if (typeof window === 'undefined' || !window.localStorage) {
+ return;
+ }
+ window.localStorage.setItem(
+ RECENT_PAGES_STORAGE_KEY,
+ JSON.stringify(entries.slice(0, MAX_RECENT_PAGES))
+ );
+}
+
+function trackRecentPage(route) {
+ if (!route || typeof route.path !== 'string') {
+ return;
+ }
+ // Ignore auth callback state because it is transient and not useful history.
+ if (typeof route.path === 'string' && route.path.includes('code=')) {
+ return;
+ }
+ const path = route.fullPath || route.path;
+ const label = formatHistoryLabel(route);
+ const visitedAt = Date.now();
+
+ const existing = safeReadRecentPages();
+ const deduped = existing.filter(entry => entry.path !== path);
+ const next = [{ path, label, visitedAt }, ...deduped].slice(0, MAX_RECENT_PAGES);
+ saveRecentPages(next);
+}
+
const app = Vue.createApp({
template: '