fix(panel): column-reverse thread keeps the bottom, no island in the plugin
This commit is contained in:
@@ -79,20 +79,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
// The scroller is column-reverse: its origin is the bottom, scrollTop is
|
||||
// zero there and goes negative upward, and the browser keeps the view at
|
||||
// the bottom while content grows - no matter how late markdown lands.
|
||||
function nearBottom(): boolean {
|
||||
if (!scroller) {
|
||||
return true;
|
||||
}
|
||||
const left =
|
||||
scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;
|
||||
return left < NEAR_BOTTOM_PX;
|
||||
return !scroller || Math.abs(scroller.scrollTop) < NEAR_BOTTOM_PX;
|
||||
}
|
||||
|
||||
function toBottom() {
|
||||
if (scroller) {
|
||||
scroller.scrollTop = scroller.scrollHeight;
|
||||
scroller.scrollTop = 0;
|
||||
}
|
||||
// Whatever actually scrolls - this box, or a host's pane around it.
|
||||
end?.scrollIntoView({ block: "end" });
|
||||
}
|
||||
|
||||
@@ -211,7 +208,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-3 py-3 @md:px-6"
|
||||
class="flex min-h-0 flex-1 flex-col-reverse overflow-y-auto overflow-x-hidden px-3 py-3 @md:px-6"
|
||||
onscroll={() => {
|
||||
if (Date.now() > settleUntil) {
|
||||
pinned = nearBottom();
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import KindMark from "$lib/shell/kind-mark.svelte";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { ConversationIndex } from "./index.svelte";
|
||||
import PanelIsland from "./panel-island.svelte";
|
||||
|
||||
let {
|
||||
client,
|
||||
@@ -35,7 +34,6 @@
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-1.5 border-b px-2 py-1.5">
|
||||
<PanelIsland compact {index} {onOpen} />
|
||||
<Popover.Root bind:open={switcherOpen}>
|
||||
<Popover.Trigger>
|
||||
{#snippet child({ props })}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { ConversationIndex } from "./index.svelte";
|
||||
|
||||
let {
|
||||
index,
|
||||
onOpen,
|
||||
compact = false,
|
||||
class: className = "",
|
||||
}: {
|
||||
index: ConversationIndex;
|
||||
onOpen: (id: string) => void;
|
||||
compact?: boolean;
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
const waiting = $derived(index.open.filter((row) => row.pending_question));
|
||||
const running = $derived(
|
||||
index.open.filter((row) => row.running_turn && !row.pending_question)
|
||||
);
|
||||
const live = $derived(index.live.state);
|
||||
const dot = $derived.by(() => {
|
||||
if (live === "failed") {
|
||||
return "bg-destructive";
|
||||
}
|
||||
if (live !== "open") {
|
||||
return "bg-warn";
|
||||
}
|
||||
return running.length > 0 ? "bg-signal animate-pulse-dot" : "bg-ok";
|
||||
});
|
||||
const headline = $derived.by(() => {
|
||||
if (live === "failed") {
|
||||
return "offline";
|
||||
}
|
||||
if (live !== "open") {
|
||||
return "connecting";
|
||||
}
|
||||
if (running.length === 0) {
|
||||
return "idle";
|
||||
}
|
||||
return running.length === 1 ? "1 in motion" : `${running.length} in motion`;
|
||||
});
|
||||
const target = $derived(waiting[0] ?? running[0] ?? null);
|
||||
const questions = $derived.by(() => {
|
||||
if (compact) {
|
||||
return String(waiting.length);
|
||||
}
|
||||
return waiting.length === 1 ? "1 question" : `${waiting.length} questions`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<button
|
||||
aria-label={target ? `Open ${target.title ?? target.kind}` : headline}
|
||||
class={cn("island", compact && "gap-1.5 px-2.5", className)}
|
||||
disabled={!target}
|
||||
onclick={() => target && onOpen(target.id)}
|
||||
title={index.live.detail ?? headline}
|
||||
type="button"
|
||||
>
|
||||
<span class={cn("size-2 shrink-0 rounded-full", dot)}></span>
|
||||
{#if !compact}
|
||||
<span class="font-medium">{headline}</span>
|
||||
{/if}
|
||||
{#if waiting.length > 0}
|
||||
{#if !compact}
|
||||
<span class="island-sep"></span>
|
||||
{/if}
|
||||
<span class="font-medium text-attention-foreground">{questions}</span>
|
||||
{/if}
|
||||
</button>
|
||||
@@ -5,7 +5,6 @@
|
||||
import ConversationView from "./conversation-view.svelte";
|
||||
import type { ConversationIndex } from "./index.svelte";
|
||||
import PanelBar from "./panel-bar.svelte";
|
||||
import PanelIsland from "./panel-island.svelte";
|
||||
|
||||
let {
|
||||
client,
|
||||
@@ -43,15 +42,15 @@
|
||||
|
||||
<!--
|
||||
impeccable direction contract (seed 071a8c77, mode operate, strip board × live activity, in Obsidian's skin)
|
||||
THESIS: the agent's front inside the vault, not a website in a pane. The island carries what is
|
||||
alive, the rail is the day's strips, the thread owns its column.
|
||||
THESIS: the agent's front inside the vault, not a website in a pane. The rail is the day's
|
||||
strips, the thread owns its column; what is alive shows on the strips themselves.
|
||||
OWN-WORLD: Obsidian's own variables through .beaver-root; strips with a state edge, hairline
|
||||
rules, tabular numerals; amber only for a question waiting; nothing decorative.
|
||||
STORY: the operator opens the tab and reads the island, picks today's strip, answers a question,
|
||||
STORY: the operator opens the tab, picks today's strip, answers a question,
|
||||
watches tools stream; beside a deep chat the sidedock shows the agent's activity, never a
|
||||
second copy of the note.
|
||||
FIRST VIEWPORT: narrow: island + switcher over the thread and the composer pinned low. Wide:
|
||||
the rail with the island on top, the thread to the right.
|
||||
FIRST VIEWPORT: narrow: the switcher over the thread and the composer pinned low. Wide:
|
||||
the rail on the left, the thread to the right.
|
||||
FORM: strip board × live activity; user-steered; seed key 071a8c77.
|
||||
FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review,
|
||||
the verdict, and DESIGN.md
|
||||
@@ -63,9 +62,6 @@ the verdict, and DESIGN.md
|
||||
<div class="flex min-h-0 flex-1">
|
||||
{#if wide}
|
||||
<aside class="flex w-80 shrink-0 flex-col border-r">
|
||||
<div class="flex items-center gap-2 px-3 pt-3 pb-1">
|
||||
<PanelIsland {index} onOpen={open} />
|
||||
</div>
|
||||
<Rail {client} {index} onOpen={open} {onOpenFile} {selected} />
|
||||
</aside>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user