Merge branch 'dev'
CI/CD / deploy (push) Successful in 2m55s Details

This commit is contained in:
Jeff Emmett 2026-04-10 17:48:41 -04:00
commit a89a6fbebb
9 changed files with 145 additions and 4 deletions

View File

@ -13,6 +13,7 @@ import type { CrowdSurfDoc, CrowdSurfPrompt, Contribution } from '../schemas';
import { getDecayProgress, getTimeRemaining, getRightSwipeCount, isReadyToTrigger, getUrgency, parseContributions, crowdsurfSchema, crowdsurfDocId } from '../schemas';
import { getModuleApiBase } from "../../../shared/url-helpers";
import type { DocumentId } from "../../../shared/local-first/document";
import { ViewHistory } from "../../../shared/view-history.js";
// ── Auth helpers ──
function getSession(): { accessToken: string; claims: { sub: string; did?: string; username?: string } } | null {
@ -45,6 +46,7 @@ class FolkCrowdSurfDashboard extends HTMLElement {
// State
private activeTab: ViewTab = 'discover';
private _history = new ViewHistory<ViewTab>('discover', 'crowdsurf');
private loading = true;
private prompts: CrowdSurfPrompt[] = [];
private currentPromptIndex = 0;
@ -95,9 +97,12 @@ class FolkCrowdSurfDashboard extends HTMLElement {
this.initMultiplayer();
}
this._stopPresence = startPresenceHeartbeat(() => ({ module: 'crowdsurf', context: this.prompts[this.currentPromptIndex]?.text || 'CrowdSurf' }));
window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
}
disconnectedCallback() {
this._history.destroy();
window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
this._stopPresence?.();
this._lfcUnsub?.();
this._lfcUnsub = null;
@ -105,6 +110,13 @@ class FolkCrowdSurfDashboard extends HTMLElement {
if (this._expiryTimer !== null) clearInterval(this._expiryTimer);
}
private _onViewRestored = (e: CustomEvent) => {
if (e.detail?.moduleId !== 'crowdsurf') return;
this.activeTab = e.detail.view;
this.render();
this.bindEvents();
};
// ── Multiplayer init ──
private async initMultiplayer() {
@ -337,6 +349,7 @@ class FolkCrowdSurfDashboard extends HTMLElement {
this.lfClient.createPrompt(prompt);
}
this._history.push('discover');
this.activeTab = 'discover';
this.render();
this.bindEvents();
@ -738,6 +751,7 @@ class FolkCrowdSurfDashboard extends HTMLElement {
btn.addEventListener('click', () => {
const tab = btn.dataset.tab as ViewTab;
if (tab && tab !== this.activeTab) {
this._history.push(tab);
this.activeTab = tab;
this.render();
this.bindEvents();
@ -761,6 +775,7 @@ class FolkCrowdSurfDashboard extends HTMLElement {
// Go to create tab
this.shadow.querySelector('[data-action="go-create"]')?.addEventListener('click', () => {
this._history.push('create');
this.activeTab = 'create';
this.render();
this.bindEvents();

View File

@ -13,6 +13,7 @@ import { startPresenceHeartbeat } from '../../../shared/collab-presence';
import { getModuleApiBase } from "../../../shared/url-helpers";
import type { DocumentId } from "../../../shared/local-first/document";
import { bnbSchema, bnbDocId } from "../schemas";
import { ViewHistory } from "../../../shared/view-history.js";
const BNB_TOUR_STEPS: TourStep[] = [
{ target: '.bnb-search', title: 'Search', message: 'Filter listings by location, type, or economy model.' },
@ -63,6 +64,7 @@ class FolkBnbView extends HTMLElement {
#stats: any = null;
#selectedStay: any = null;
#view: 'grid' | 'map' = 'grid';
private _history = new ViewHistory<'grid' | 'map'>('grid', 'rbnb');
#search = '';
#typeFilter = '';
#economyFilter = '';
@ -78,13 +80,23 @@ class FolkBnbView extends HTMLElement {
this.#loadData();
this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rbnb', context: 'Listings' }));
if (this.#space !== 'demo') this.subscribeOffline();
window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
}
disconnectedCallback() {
this._history.destroy();
window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
this._stopPresence?.();
this._offlineUnsub?.(); this._offlineUnsub = null;
}
private _onViewRestored = (e: CustomEvent) => {
if (e.detail?.moduleId !== 'rbnb') return;
this.#view = e.detail.view;
this.#render();
this.#renderContent();
};
private async subscribeOffline() {
const runtime = (window as any).__rspaceOfflineRuntime;
if (!runtime?.isInitialized) return;
@ -230,7 +242,9 @@ class FolkBnbView extends HTMLElement {
// View toggles
for (const btn of this.querySelectorAll('.bnb-view__toggle')) {
btn.addEventListener('click', () => {
this.#view = (btn as HTMLElement).dataset.view as 'grid' | 'map';
const v = (btn as HTMLElement).dataset.view as 'grid' | 'map';
this._history.push(v);
this.#view = v;
this.#render();
this.#renderContent();
});

View File

@ -25,6 +25,7 @@ import {
calculateAffordability,
} from '../lib/mortgage-engine';
import type { MortgageSummary } from '../lib/mortgage-engine';
import { ViewHistory } from "../../../shared/view-history.js";
type ViewMode = 'mycelial' | 'flow' | 'grid' | 'lender' | 'borrower';
@ -90,6 +91,7 @@ class FolkMortgageSimulator extends HTMLElement {
private summary!: MortgageSummary;
private currentMonth = DEFAULT_CONFIG.startMonth;
private viewMode: ViewMode = 'mycelial';
private _history = new ViewHistory<ViewMode>('mycelial', 'rflows');
private selectedTrancheId: string | null = null;
private controlsOpen = true;
private playing = false;
@ -121,12 +123,26 @@ class FolkMortgageSimulator extends HTMLElement {
this._recompute();
this._attachListeners();
this.render();
window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
}
disconnectedCallback() {
this._history.destroy();
window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
this._stopPlayback();
}
private _onViewRestored = (e: CustomEvent) => {
if (e.detail?.moduleId !== 'rflows') return;
this.viewMode = e.detail.view;
const tabs = this.shadow.querySelectorAll('.view-tab');
tabs.forEach((tab: Element) => {
const t = tab as HTMLElement;
t.className = `view-tab ${t.dataset.view === this.viewMode ? 'active' : ''}`;
});
this._updateView();
};
// ─── Core simulation ──────────────────────────────────
private _recompute() {
@ -1548,6 +1564,7 @@ class FolkMortgageSimulator extends HTMLElement {
switch (action) {
case 'view': {
this._history.push(target.dataset.view as ViewMode);
this.viewMode = target.dataset.view as ViewMode;
// Update tab highlights in-place
const tabs = this.shadow.querySelectorAll('.view-tab');

View File

@ -70,11 +70,13 @@ import { TourEngine } from "../../../shared/tour-engine";
import { startPresenceHeartbeat } from '../../../shared/collab-presence';
import type { DocumentId } from "../../../shared/local-first/document";
import { networkSchema, networkDocId } from "../schemas";
import { ViewHistory } from "../../../shared/view-history.js";
class FolkCrmView extends HTMLElement {
private shadow: ShadowRoot;
private space = "";
private activeTab: Tab = "pipeline";
private _history = new ViewHistory<Tab>("pipeline", "rnetwork");
private searchQuery = "";
private sortColumn = "";
private sortAsc = true;
@ -129,6 +131,7 @@ class FolkCrmView extends HTMLElement {
private _onTabChange = (e: Event) => {
const tab = (e as CustomEvent).detail?.tab;
if (tab && tab !== this.activeTab) {
this._history.push(tab as Tab);
this.activeTab = tab as Tab;
this.searchQuery = "";
this.sortColumn = "";
@ -164,13 +167,23 @@ class FolkCrmView extends HTMLElement {
setTimeout(() => this._tour.start(), 1200);
}
this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rnetwork', context: this.graphSelectedId ? 'CRM' : `CRM - ${this.activeTab}` }));
window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
}
disconnectedCallback() {
this._history.destroy();
window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
document.removeEventListener("rapp-tab-change", this._onTabChange);
this._stopPresence?.();
}
private _onViewRestored = (e: CustomEvent) => {
if (e.detail?.moduleId !== 'rnetwork') return;
this.activeTab = e.detail.view;
this.searchQuery = "";
this.render();
};
private async subscribeCollabOverlay() {
const runtime = (window as any).__rspaceOfflineRuntime;
if (!runtime?.isInitialized) return;

View File

@ -156,6 +156,7 @@ import type { SwagDoc, SwagDesign } from "../schemas";
import { swagSchema, swagDocId } from "../schemas";
import { startPresenceHeartbeat } from '../../../shared/collab-presence';
import type { DocumentId } from "../../../shared/local-first/document";
import { ViewHistory } from "../../../shared/view-history.js";
// Auth helpers
function getSession(): { accessToken: string; claims: { sub: string; did?: string; username?: string } } | null {
@ -195,6 +196,7 @@ class FolkSwagDesigner extends HTMLElement {
// Tab state
private activeTab: TabId = "browse";
private _history = new ViewHistory<TabId>("browse", "rswag");
// Demo state
private selectedProduct = "tee";
@ -284,15 +286,24 @@ class FolkSwagDesigner extends HTMLElement {
setTimeout(() => this._tour.start(), 1200);
}
this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rswag', context: 'Swag Designer' }));
window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
}
disconnectedCallback() {
this._history.destroy();
window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
this._stopPresence?.();
this._lfcUnsub?.();
this._lfcUnsub = null;
this.lfClient?.disconnect();
}
private _onViewRestored = (e: CustomEvent) => {
if (e.detail?.moduleId !== 'rswag') return;
this.activeTab = e.detail.view;
this.render();
};
private async initMultiplayer() {
try {
this.lfClient = new SwagLocalFirstClient(this.space);
@ -929,7 +940,9 @@ class FolkSwagDesigner extends HTMLElement {
// Tab switching
this.shadow.querySelectorAll<HTMLElement>(".tab-btn").forEach(btn => {
btn.addEventListener("click", () => {
this.activeTab = btn.dataset.tab as TabId;
const tab = btn.dataset.tab as TabId;
this._history.push(tab);
this.activeTab = tab;
this.render();
});
});
@ -938,7 +951,9 @@ class FolkSwagDesigner extends HTMLElement {
this.shadow.querySelectorAll<HTMLElement>("[data-tab]").forEach(btn => {
if (!btn.classList.contains("tab-btn")) {
btn.addEventListener("click", () => {
this.activeTab = btn.dataset.tab as TabId;
const tab = btn.dataset.tab as TabId;
this._history.push(tab);
this.activeTab = tab;
this.render();
});
}
@ -1014,6 +1029,7 @@ class FolkSwagDesigner extends HTMLElement {
this.shadow.querySelectorAll<HTMLElement>(".dither-btn").forEach(btn => {
btn.addEventListener("click", () => {
this.ditherDesignSlug = btn.dataset.slug || "";
this._history.push("dither");
this.activeTab = "dither";
this.render();
});

View File

@ -8,6 +8,7 @@
import type { DocumentId } from "../../../shared/local-first/document";
import { startPresenceHeartbeat } from '../../../shared/collab-presence';
import { commitmentsSchema, commitmentsDocId } from "../schemas";
import { ViewHistory } from "../../../shared/view-history.js";
// ── Constants ──
@ -254,6 +255,7 @@ class FolkTimebankApp extends HTMLElement {
private shadow: ShadowRoot;
private space = 'demo';
private currentView: 'canvas' | 'collaborate' | 'dashboard' = 'canvas';
private _history = new ViewHistory<'canvas' | 'collaborate' | 'dashboard'>('canvas', 'rtime');
// Pool panel state
private canvas!: HTMLCanvasElement;
@ -357,6 +359,7 @@ class FolkTimebankApp extends HTMLElement {
this.fetchData();
this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rtime', context: 'Timebank' }));
if (this.space !== 'demo') this.subscribeOffline();
window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
}
private async subscribeOffline() {
@ -392,10 +395,28 @@ class FolkTimebankApp extends HTMLElement {
}
disconnectedCallback() {
this._history.destroy();
window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
if (this.animFrame) cancelAnimationFrame(this.animFrame);
this._stopPresence?.(); this._offlineUnsub?.(); this._offlineUnsub = null;
}
private _onViewRestored = (e: CustomEvent) => {
if (e.detail?.moduleId !== 'rtime') return;
this.currentView = e.detail.view;
// Toggle visibility of view panels
const canvasView = this.shadow.getElementById('canvas-view');
const collabView = this.shadow.getElementById('collaborate-view');
const dashView = this.shadow.getElementById('dashboard-view');
if (canvasView) canvasView.style.display = this.currentView === 'canvas' ? 'flex' : 'none';
if (collabView) collabView.style.display = this.currentView === 'collaborate' ? 'flex' : 'none';
if (dashView) dashView.style.display = this.currentView === 'dashboard' ? 'flex' : 'none';
this.shadow.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', (t as HTMLElement).dataset.view === this.currentView));
if (this.currentView === 'canvas') { this.resizePoolCanvas(); this.rebuildSidebar(); }
if (this.currentView === 'collaborate') this.refreshCollaborate();
if (this.currentView === 'dashboard') this.refreshDashboard();
};
private async fetchData() {
const base = this.getApiBase();
try {
@ -707,6 +728,7 @@ class FolkTimebankApp extends HTMLElement {
tab.addEventListener('click', () => {
const view = (tab as HTMLElement).dataset.view as 'canvas' | 'collaborate' | 'dashboard';
if (view === this.currentView) return;
this._history.push(view);
this.currentView = view;
this.shadow.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', (t as HTMLElement).dataset.view === view));
const canvasView = this.shadow.getElementById('canvas-view')!;

View File

@ -10,6 +10,7 @@ import { authFetch, requireAuth } from "../../../shared/auth-fetch";
import { startPresenceHeartbeat } from '../../../shared/collab-presence';
import type { DocumentId } from "../../../shared/local-first/document";
import { tubeSchema, tubeDocId } from "../schemas";
import { ViewHistory } from "../../../shared/view-history.js";
class FolkVideoPlayer extends HTMLElement {
private shadow: ShadowRoot;
@ -17,6 +18,7 @@ class FolkVideoPlayer extends HTMLElement {
private videos: Array<{ name: string; size: number; duration?: string; date?: string }> = [];
private currentVideo: string | null = null;
private mode: "library" | "live" | "360live" = "library";
private _history = new ViewHistory<"library" | "live" | "360live">("library", "rtube");
private streamKey = "";
private searchTerm = "";
private isDemo = false;
@ -63,13 +65,25 @@ class FolkVideoPlayer extends HTMLElement {
setTimeout(() => this._tour.start(), 1200);
}
this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rtube', context: this.currentVideo || 'Video' }));
window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
}
disconnectedCallback() {
this._history.destroy();
window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
this._stopPresence?.();
this._offlineUnsub?.(); this._offlineUnsub = null;
}
private _onViewRestored = (e: CustomEvent) => {
if (e.detail?.moduleId !== 'rtube') return;
if (this.mode === "360live" && e.detail.view !== "360live") {
this.destroyHlsPlayers();
}
this.mode = e.detail.view;
this.render();
};
private async subscribeOffline() {
const runtime = (window as any).__rspaceOfflineRuntime;
if (!runtime?.isInitialized) return;
@ -587,6 +601,7 @@ class FolkVideoPlayer extends HTMLElement {
if (this.mode === "360live" && newMode !== "360live") {
this.destroyHlsPlayers();
}
this._history.push(newMode);
this.mode = newMode;
this.render();
});

View File

@ -13,6 +13,7 @@ import { startPresenceHeartbeat } from '../../../shared/collab-presence';
import { getModuleApiBase } from "../../../shared/url-helpers";
import type { DocumentId } from "../../../shared/local-first/document";
import { vnbSchema, vnbDocId } from "../schemas";
import { ViewHistory } from "../../../shared/view-history.js";
const VNB_TOUR_STEPS: TourStep[] = [
{ target: '.vnb-search', title: 'Search', message: 'Filter by vehicle type, dates, or economy model.' },
@ -68,6 +69,7 @@ class FolkVnbView extends HTMLElement {
#stats: any = null;
#selectedRental: any = null;
#view: 'grid' | 'map' = 'grid';
private _history = new ViewHistory<'grid' | 'map'>('grid', 'rvnb');
#search = '';
#typeFilter = '';
#economyFilter = '';
@ -83,13 +85,23 @@ class FolkVnbView extends HTMLElement {
this.#loadData();
this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rvnb', context: 'Venues' }));
if (this.#space !== 'demo') this.subscribeOffline();
window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
}
disconnectedCallback() {
this._history.destroy();
window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
this._stopPresence?.();
this._offlineUnsub?.(); this._offlineUnsub = null;
}
private _onViewRestored = (e: CustomEvent) => {
if (e.detail?.moduleId !== 'rvnb') return;
this.#view = e.detail.view;
this.#render();
this.#renderContent();
};
private async subscribeOffline() {
const runtime = (window as any).__rspaceOfflineRuntime;
if (!runtime?.isInitialized) return;
@ -234,7 +246,9 @@ class FolkVnbView extends HTMLElement {
// View toggles
for (const btn of this.querySelectorAll('.vnb-view__toggle')) {
btn.addEventListener('click', () => {
this.#view = (btn as HTMLElement).dataset.view as 'grid' | 'map';
const v = (btn as HTMLElement).dataset.view as 'grid' | 'map';
this._history.push(v);
this.#view = v;
this.#render();
this.#renderContent();
});

View File

@ -18,6 +18,7 @@ import type { WalletDoc, WatchedAddress } from "../schemas";
import { walletSchema, walletDocId } from "../schemas";
import { startPresenceHeartbeat } from '../../../shared/collab-presence';
import type { DocumentId } from "../../../shared/local-first/document";
import { ViewHistory } from "../../../shared/view-history.js";
interface ChainInfo {
chainId: string;
@ -197,6 +198,7 @@ class FolkWalletViewer extends HTMLElement {
// Visualization state
private activeView: ViewTab = "budget";
private _history = new ViewHistory<ViewTab>("budget", "rwallet");
private transfers: Map<string, any> | null = null;
private transfersLoading = false;
private d3Ready = false;
@ -265,9 +267,12 @@ class FolkWalletViewer extends HTMLElement {
setTimeout(() => this._tour.start(), 1200);
}
this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rwallet', context: 'Wallet' }));
window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
}
disconnectedCallback() {
this._history.destroy();
window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
this._stopPresence?.();
if (this.flowsPlayInterval) {
clearInterval(this.flowsPlayInterval);
@ -275,6 +280,15 @@ class FolkWalletViewer extends HTMLElement {
}
}
private _onViewRestored = (e: CustomEvent) => {
if (e.detail?.moduleId !== 'rwallet') return;
this.activeView = e.detail.view;
this.render();
if (this.activeView !== "balances") {
requestAnimationFrame(() => this.drawActiveVisualization());
}
};
private checkAuthState() {
try {
const session = localStorage.getItem("encryptid_session");
@ -1265,6 +1279,7 @@ class FolkWalletViewer extends HTMLElement {
private handleViewTabClick(view: ViewTab) {
if (this.activeView === view) return;
this._history.push(view);
this.activeView = view;
if (view !== "balances" && !this.transfers && !this.isDemo) {