Initial Lime window manager

This commit is contained in:
2026-10-02 14:14:47 -05:00
commit ccd691edd0
121 changed files with 29719 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
<div id="lime-activity-workflow-proposal">
<style>
#lime-activity-workflow-proposal .monitor-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
gap: 16px;
margin: 16px 0 8px;
}
#lime-activity-workflow-proposal .monitor { min-width: 0; }
#lime-activity-workflow-proposal svg { display: block; width: 100%; }
#lime-activity-workflow-proposal svg text { fill: var(--foreground); }
#lime-activity-workflow-proposal .monitor-outline {
fill: none;
stroke: var(--border);
}
#lime-activity-workflow-proposal .window {
fill: color-mix(in srgb, var(--viz-series-1) 12%, transparent);
stroke: var(--border);
}
</style>
<div class="viz-controls">
<label class="form-label">Proposed behavior · scenario
<select class="form-select" data-scenario>
<optgroup label="Everyday workflow">
<option value="0">1 · Start: Lime left, Chill right</option>
<option value="1">2 · Open Lime on the right too</option>
<option value="2">3 · Open Docs on the right</option>
<option value="3">4 · Switch right to Chill</option>
<option value="4">5 · Switch left to Work too</option>
<option value="5">6 · Return to Lime on the right</option>
<option value="6">7 · Return to Lime on the left too</option>
<option value="7">8 · Move Docs to the left</option>
<option value="8">9 · Right: away, then back to Lime</option>
</optgroup>
<optgroup label="A separate edge case to decide">
<option value="9">First visit on right; Lime inactive</option>
</optgroup>
</select>
</label>
</div>
<div class="monitor-grid">
<div class="monitor" data-head="left"></div>
<div class="monitor" data-head="right"></div>
</div>
<div class="text-small" data-detail role="status" aria-live="polite"></div>
</div>
<script>
(() => {
const root = document.getElementById('lime-activity-workflow-proposal');
const choose = root.querySelector('[data-scenario]');
const detail = root.querySelector('[data-detail]');
const panels = ['left', 'right'].map(head => root.querySelector(`[data-head="${head}"]`));
const steps = [
{left:['Lime',['Editor']], right:['Chill',['Chat']], detail:'Lime has only been used on the left so far.'},
{left:['Lime',['Editor']], right:['Lime',[]], detail:'Same activity, additional space. Editor stays on the left.'},
{left:['Lime',['Editor']], right:['Lime',['Docs']], detail:'Both windows belong to Lime; each has one current location.'},
{left:['Lime',['Editor']], right:['Chill',['Chat']], detail:'Docs stays open but is not displayed; its right-hand placement is remembered.'},
{left:['Work',['Work browser']], right:['Chill',['Chat']], detail:'Lime is not displayed. Editor and Docs stay open with their positions remembered.'},
{left:['Work',['Work browser']], right:['Lime',['Docs']], detail:'Docs returns on the right. Editor remains out of sight on the left.'},
{left:['Lime',['Editor']], right:['Lime',['Docs']], detail:'Both halves of the live arrangement are back. No saved View was needed.'},
{left:['Lime',['Editor','Docs']], right:['Lime',[]], detail:'The move updates Lime’s live arrangement: Docs no longer belongs in the right-hand layout.'},
{left:['Lime',['Editor','Docs']], right:['Lime',[]], detail:'No reclaim attempt or conflict dialog. The right-hand layout is still empty.'},
{left:['Work',['Work browser']], right:['Lime',[]], detail:'Edge case: Lime’s Editor is remembered on the left, not automatically brought to the right.'}
];
const ns = 'http://www.w3.org/2000/svg';
function element(tag, attributes, text) {
const node = document.createElementNS(ns, tag);
for (const [key,value] of Object.entries(attributes || {})) node.setAttribute(key, value);
if (text !== undefined) node.textContent = text;
return node;
}
function drawMonitor(panel, side, state) {
const width = Math.max(1, panel.getBoundingClientRect().width);
const height = 204;
const svg = element('svg', {viewBox:`0 0 ${width} ${height}`, height,
role:'img', 'aria-label':`${side} monitor showing ${state[0]}: ${state[1].join(' and ') || 'empty'}`});
svg.append(element('rect',{x:1,y:1,width:width-2,height:height-2,rx:4,class:'monitor-outline'}));
svg.append(element('text',{x:12,y:26,'font-weight':500},`${side} · ${state[0]}`));
svg.append(element('line',{x1:1,y1:39,x2:width-1,y2:39,stroke:'var(--border)'}));
const windows = state[1];
if (!windows.length) {
svg.append(element('text',{x:width/2,y:122,'text-anchor':'middle'},'Empty live arrangement'));
} else {
const gap = 8;
const tileWidth = (width-24-gap*(windows.length-1))/windows.length;
windows.forEach((name,index) => {
const x = 12+index*(tileWidth+gap);
svg.append(element('rect',{x,y:51,width:tileWidth,height:140,rx:2,class:'window'}));
svg.append(element('text',{x:x+tileWidth/2,y:126,'text-anchor':'middle'},name));
});
}
panel.replaceChildren(svg);
}
function draw() {
const step = steps[Number(choose.value)];
drawMonitor(panels[0],'Left',step.left);
drawMonitor(panels[1],'Right',step.right);
detail.textContent = step.detail;
}
choose.addEventListener('change', draw);
new ResizeObserver(draw).observe(root);
draw();
})();
</script>