feat(views): cross-origin embed factory + Timelapse/AI Usage views

This commit is contained in:
root
2026-06-08 14:50:14 +10:00
parent 8222717fd4
commit 96b5e6a879
4 changed files with 67 additions and 0 deletions

6
public/views/aiusage.js Normal file
View File

@@ -0,0 +1,6 @@
// public/views/aiusage.js — #/ai-usage (phuryn claude-usage)
import { embedView } from './embed.js';
export const render = embedView({
title: 'AI Usage', sub: 'claude code · token usage',
src: 'https://aiusage.hynesy.com/'
});

21
public/views/embed.js Normal file
View File

@@ -0,0 +1,21 @@
// Shared cross-origin app embed: a Void header bar + a full-height iframe.
// Reuses the Terminal tab's themed classes (.term-bar/.term-frame/.ghost).
// The embedded app lives at its own HTTPS origin, so visiting that origin
// directly shows no Void chrome — there is no "back to Void" affordance here.
import { el, mount } from '../dom.js';
export function embedView({ title, sub, src, allow }) {
return async function render(main) {
mount(main,
el('div', { class: 'term-bar' },
el('span', { class: 'term-title' }, '◆ ' + title),
sub ? el('span', { class: 'muted', style: { fontSize: '11px' } }, sub) : null,
el('a', { class: 'ghost', style: { marginLeft: 'auto' }, href: src, target: '_blank', rel: 'noopener' }, '↗ Open'),
el('button', { class: 'ghost', onclick: () => {
const f = document.getElementById('embed-frame'); if (f) f.src = f.src;
} }, '⟳ Reload')
),
el('iframe', { id: 'embed-frame', src, class: 'term-frame', ...(allow ? { allow } : {}) })
);
};
}

View File

@@ -0,0 +1,6 @@
// public/views/timelapse.js — #/timelapse
import { embedView } from './embed.js';
export const render = embedView({
title: 'Timelapse', sub: 'farm · 4K timelapse',
src: 'https://timelapse.hynesy.com/', allow: 'fullscreen'
});

View File

@@ -0,0 +1,34 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { JSDOM } from 'jsdom';
beforeAll(() => {
const dom = new JSDOM('<!doctype html><html><body><div id="main"></div></body></html>', { url: 'http://localhost/' });
global.window = dom.window; global.document = dom.window.document;
global.Node = dom.window.Node; global.location = dom.window.location;
});
afterAll(() => { delete global.window; delete global.document; delete global.Node; delete global.location; });
describe('embedView + wrappers', () => {
it('mounts an iframe with the given src + matching Open link', async () => {
const { embedView } = await import('../../public/views/embed.js');
const main = document.getElementById('main');
await embedView({ title: 'Timelapse', sub: 'x', src: 'https://timelapse.hynesy.com/', allow: 'fullscreen' })(main);
const frame = main.querySelector('iframe.term-frame');
expect(frame.getAttribute('src')).toBe('https://timelapse.hynesy.com/');
expect(frame.getAttribute('allow')).toBe('fullscreen');
expect(main.querySelector('a.ghost').getAttribute('href')).toBe('https://timelapse.hynesy.com/');
expect(main.querySelector('.term-title').textContent).toContain('Timelapse');
});
it('timelapse wrapper points at timelapse.hynesy.com', async () => {
const main = document.getElementById('main');
await (await import('../../public/views/timelapse.js')).render(main);
expect(main.querySelector('iframe.term-frame').getAttribute('src')).toBe('https://timelapse.hynesy.com/');
});
it('aiusage wrapper points at aiusage.hynesy.com', async () => {
const main = document.getElementById('main');
await (await import('../../public/views/aiusage.js')).render(main);
expect(main.querySelector('iframe.term-frame').getAttribute('src')).toBe('https://aiusage.hynesy.com/');
});
});