skaband: move timeline viewer from sketch/webui to resolve bundle conflicts

Move read-only timeline viewer functionality from sketch/webui to skaband
to eliminate custom element registration conflicts when using newsessions
with newui=true parameter.

Changes made:
- Created skaband/sketch-timeline-readonly.ts with timeline viewer functionality
- Updated skaband/messages.gohtml to use local sketch-timeline-readonly.js bundle
- Removed messages-viewer.ts from sketch/webui build configuration
- Updated Makefile to remove messages-viewer bundle extraction steps
- Added sketch-timeline-readonly.ts to skaband build.js entry points

Files created:
- skaband/sketch-timeline-readonly.ts: Timeline viewer component for archived sessions
- skaband/sketch-timeline-readonly.test.ts: Test coverage for timeline functionality

Files removed:
- sketch/webui/src/messages-viewer.ts: Relocated to skaband
- sketch/webui/src/messages-viewer.test.ts: Relocated to skaband

Build configuration updates:
- sketch/webui/esbuild.go: Remove messages-viewer.ts from bundleTs arrays
- sketch/webui/package.json: Remove messages-viewer.js export
- skaband/build.js: Add sketch-timeline-readonly.ts entry point
- Makefile: Remove messages-viewer bundle extraction from build and prepare-deploy

The timeline viewer is only used by skaband for archived session viewing,
so co-locating it with skaband eliminates the architectural issue where
both messages-viewer.js and newsessions-components.js contained overlapping
custom elements (sketch-timeline, sketch-tool-card-*, etc.) causing
registration conflicts.

This change resolves bundle conflicts on /newsessions?newui=true while
maintaining identical functionality for archived message viewing.

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: sd62ec3594672899ek
diff --git a/webui/esbuild.go b/webui/esbuild.go
index bf4cca9..246f9cb 100644
--- a/webui/esbuild.go
+++ b/webui/esbuild.go
@@ -275,7 +275,6 @@
 		"src/web-components/sketch-app-shell.ts",
 		"src/web-components/mobile-app-shell.ts",
 		"src/web-components/sketch-monaco-view.ts",
-		"src/messages-viewer.ts",
 		"node_modules/monaco-editor/esm/vs/editor/editor.worker.js",
 		"node_modules/monaco-editor/esm/vs/language/typescript/ts.worker.js",
 		"node_modules/monaco-editor/esm/vs/language/html/html.worker.js",
@@ -508,7 +507,6 @@
 		"src/web-components/sketch-app-shell.ts",
 		"src/web-components/mobile-app-shell.ts",
 		"src/web-components/sketch-monaco-view.ts",
-		"src/messages-viewer.ts",
 	}
 	metafiles := make([]string, len(bundleTs))
 
diff --git a/webui/package.json b/webui/package.json
index c62b08c..c2f15aa 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -7,7 +7,6 @@
   "main": "dist/index.js",
   "exports": {
     ".": "./dist/index.js",
-    "./messages-viewer.js": "./dist/messages-viewer.js",
     "./sketch-app-shell.js": "./dist/sketch-app-shell.js",
     "./web-components/*": "./dist/web-components/*.js"
   },
diff --git a/webui/src/messages-viewer.test.ts b/webui/src/messages-viewer.test.ts
deleted file mode 100644
index bd499f8..0000000
--- a/webui/src/messages-viewer.test.ts
+++ /dev/null
@@ -1,223 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-import { test, expect } from "@sand4rt/experimental-ct-web";
-import { State } from "./types";
-
-// Test the messages-viewer logic without importing the actual module
-// to avoid decorator issues in the test environment
-
-// Helper function to create mock session data
-function createMockSessionData(overrides: any = {}) {
-  return {
-    session_id: "test-session-123",
-    user_name: "john.doe",
-    user_email: "john.doe@example.com",
-    session_state: {
-      git_username: "john.doe",
-      git_origin: "https://github.com/example/repo.git",
-      session_id: "test-session-123",
-      ...overrides.session_state,
-    },
-    ...overrides,
-  };
-}
-
-// Helper function to create mock view data
-function createMockViewData(overrides: any = {}) {
-  return {
-    SessionWithData: overrides.SessionWithData || createMockSessionData(),
-    Messages: [
-      {
-        type: "user",
-        content: "Hello, this is a test user message",
-        timestamp: new Date().toISOString(),
-        conversation_id: "test-conv",
-        idx: 1,
-        hide_output: false,
-      },
-      {
-        type: "agent",
-        content: "This is an agent response",
-        timestamp: new Date().toISOString(),
-        conversation_id: "test-conv",
-        idx: 2,
-        hide_output: false,
-      },
-      ...(overrides.Messages || []),
-    ],
-    ToolResults: overrides.ToolResults || {},
-  };
-}
-
-// Test the state creation logic directly
-function createStateFromViewData(viewData: any): Partial<State> {
-  const sessionWithData = viewData.SessionWithData;
-  return {
-    session_id: sessionWithData?.session_id || "",
-    git_username:
-      sessionWithData?.session_state?.git_username ||
-      sessionWithData?.user_name,
-    git_origin: sessionWithData?.session_state?.git_origin,
-  };
-}
-
-test("creates proper state object with git_username from session_state", () => {
-  const mockViewData = createMockViewData();
-  const state = createStateFromViewData(mockViewData);
-
-  expect(state.session_id).toBe("test-session-123");
-  expect(state.git_username).toBe("john.doe");
-  expect(state.git_origin).toBe("https://github.com/example/repo.git");
-});
-
-test("uses git_username from session_state when available", () => {
-  const mockViewData = createMockViewData({
-    SessionWithData: createMockSessionData({
-      user_name: "fallback.user",
-      session_state: {
-        git_username: "primary.user", // This should take precedence
-      },
-    }),
-  });
-
-  const state = createStateFromViewData(mockViewData);
-  expect(state.git_username).toBe("primary.user");
-});
-
-test("falls back to user_name when session_state.git_username not available", () => {
-  const mockViewData = createMockViewData({
-    SessionWithData: createMockSessionData({
-      user_name: "fallback.user",
-      session_state: {
-        // git_username not provided
-      },
-    }),
-  });
-
-  const state = createStateFromViewData(mockViewData);
-  expect(state.git_username).toBe("fallback.user");
-});
-
-test("handles missing git username gracefully", () => {
-  const mockViewData = createMockViewData({
-    SessionWithData: {
-      session_id: "test-session-123",
-      // user_name not provided
-      session_state: {
-        // git_username not provided
-      },
-    },
-  });
-
-  const state = createStateFromViewData(mockViewData);
-  expect(state.git_username).toBeUndefined();
-});
-
-test("message filtering logic works correctly", () => {
-  const messages = [
-    {
-      type: "user",
-      content: "Visible message",
-      hide_output: false,
-      timestamp: new Date().toISOString(),
-      conversation_id: "test-conv",
-      idx: 1,
-    },
-    {
-      type: "agent",
-      content: "Hidden message",
-      hide_output: true, // This should be filtered out
-      timestamp: new Date().toISOString(),
-      conversation_id: "test-conv",
-      idx: 2,
-    },
-    {
-      type: "agent",
-      content: "Another visible message",
-      hide_output: false,
-      timestamp: new Date().toISOString(),
-      conversation_id: "test-conv",
-      idx: 3,
-    },
-  ];
-
-  // Test the filtering logic
-  const visibleMessages = messages.filter((msg: any) => !msg.hide_output);
-
-  expect(visibleMessages).toHaveLength(2);
-  expect(visibleMessages[0].content).toBe("Visible message");
-  expect(visibleMessages[1].content).toBe("Another visible message");
-});
-
-test("handles empty or malformed session data", () => {
-  const mockViewData = {
-    SessionWithData: null, // Malformed data
-    Messages: [],
-    ToolResults: {},
-  };
-
-  // Should not throw an error
-  expect(() => {
-    const state = createStateFromViewData(mockViewData);
-    expect(state.session_id).toBe("");
-    expect(state.git_username).toBeUndefined();
-  }).not.toThrow();
-});
-
-test("preserves git_origin from session state", () => {
-  const mockViewData = createMockViewData({
-    SessionWithData: createMockSessionData({
-      session_state: {
-        git_origin: "https://github.com/test/repository.git",
-        git_username: "test.user",
-      },
-    }),
-  });
-
-  const state = createStateFromViewData(mockViewData);
-  expect(state.git_origin).toBe("https://github.com/test/repository.git");
-});
-
-test("fallback hierarchy works correctly", () => {
-  // Test all combinations of the fallback hierarchy
-  const testCases = [
-    {
-      name: "session_state.git_username takes precedence",
-      sessionData: {
-        user_name: "fallback",
-        session_state: { git_username: "primary" },
-      },
-      expected: "primary",
-    },
-    {
-      name: "user_name when session_state.git_username missing",
-      sessionData: {
-        user_name: "fallback",
-        session_state: {},
-      },
-      expected: "fallback",
-    },
-    {
-      name: "undefined when both missing",
-      sessionData: {
-        session_id: "test-session-123",
-        // user_name not provided
-        session_state: {
-          // git_username not provided
-        },
-      },
-      expected: undefined,
-    },
-  ];
-
-  testCases.forEach(({ name, sessionData, expected }) => {
-    const mockViewData = createMockViewData({
-      SessionWithData:
-        name === "undefined when both missing"
-          ? sessionData
-          : createMockSessionData(sessionData),
-    });
-
-    const state = createStateFromViewData(mockViewData);
-    expect(state.git_username).toBe(expected);
-  });
-});
diff --git a/webui/src/messages-viewer.ts b/webui/src/messages-viewer.ts
deleted file mode 100644
index c374646..0000000
--- a/webui/src/messages-viewer.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-import { SketchTimeline } from "./web-components/sketch-timeline";
-import { aggregateAgentMessages } from "./web-components/aggregateAgentMessages";
-import { State } from "./types";
-
-// Ensure this dependency ends up in the bundle (the "as SketchTimeline" reference below
-// is insufficient for the bundler to include it).
-// eslint-disable-next-line @typescript-eslint/no-unused-expressions
-SketchTimeline;
-
-export function renderMessagesViewer(viewData: any, container: HTMLDivElement) {
-  const timelineEl = document.createElement(
-    "sketch-timeline",
-  ) as SketchTimeline;
-  // Filter out hidden messages at the display level (matches sketch behavior)
-  const visibleMessages = viewData.Messages.filter(
-    (msg: any) => !msg.hide_output,
-  );
-  const messages = aggregateAgentMessages(visibleMessages, []);
-  timelineEl.messages = messages;
-  timelineEl.toolCalls = viewData.ToolResults;
-  timelineEl.scrollContainer = { value: window.document.body };
-
-  // Create a state object for the timeline component
-  // This ensures user attribution works in archived messages
-  const sessionWithData = viewData.SessionWithData;
-  const state: Partial<State> = {
-    session_id: sessionWithData?.session_id || "",
-    // Use git_username from session state if available, fallback to UserName field, then extract from session info
-    git_username:
-      sessionWithData?.session_state?.git_username ||
-      sessionWithData?.user_name ||
-      extractGitUsername(sessionWithData),
-    // Include other relevant state fields that might be available
-    git_origin: sessionWithData?.session_state?.git_origin,
-  };
-
-  timelineEl.state = state as State;
-  container.replaceWith(timelineEl);
-}
-
-// Helper function to extract git username from session data
-function extractGitUsername(sessionWithData: any): string | undefined {
-  // Try to extract from session state first
-  if (sessionWithData?.session_state?.git_username) {
-    return sessionWithData.session_state.git_username;
-  }
-
-  // For older sessions, we might not have git_username stored
-  // We could try to extract it from other sources, but for now return undefined
-  return undefined;
-}
-
-window.globalThis.renderMessagesViewer = renderMessagesViewer;
diff --git a/webui/src/web-components/sketch-timeline-message.test.ts b/webui/src/web-components/sketch-timeline-message.test.ts
index d487fce..3ef8d5e 100644
--- a/webui/src/web-components/sketch-timeline-message.test.ts
+++ b/webui/src/web-components/sketch-timeline-message.test.ts
@@ -3,7 +3,6 @@
 // NOTE: Most tests in this file are currently skipped due to TypeScript decorator
 // configuration issues in the test environment. The git username attribution
 // functionality has been tested manually and works correctly in runtime.
-// The core logic is tested in messages-viewer.test.ts
 import { SketchTimelineMessage } from "./sketch-timeline-message";
 import {
   AgentMessage,