Files
adolf/extensions/line/src/flex-templates/schedule-cards.ts
alvis bedb527145
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Vendor OpenClaw source as Adolf fork baseline
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
2026-07-05 09:36:54 +00:00

469 lines
11 KiB
TypeScript

// Line plugin module implements schedule cards behavior.
import { attachFooterText } from "./common.js";
import type { Action, FlexBox, FlexBubble, FlexComponent, FlexText } from "./types.js";
function buildTitleSubtitleHeader(params: { title: string; subtitle?: string }): FlexComponent[] {
const { title, subtitle } = params;
const headerContents: FlexComponent[] = [
{
type: "text",
text: title,
weight: "bold",
size: "xl",
color: "#111111",
wrap: true,
} as FlexText,
];
if (subtitle) {
headerContents.push({
type: "text",
text: subtitle,
size: "sm",
color: "#888888",
margin: "sm",
wrap: true,
} as FlexText);
}
return headerContents;
}
function buildCardHeaderSections(headerContents: FlexComponent[]): FlexComponent[] {
return [
{
type: "box",
layout: "vertical",
contents: headerContents,
paddingBottom: "lg",
} as FlexBox,
{
type: "separator",
color: "#EEEEEE",
},
];
}
function createMegaBubbleWithFooter(params: {
bodyContents: FlexComponent[];
footer?: string;
}): FlexBubble {
const bubble: FlexBubble = {
type: "bubble",
size: "mega",
body: {
type: "box",
layout: "vertical",
contents: params.bodyContents,
paddingAll: "xl",
backgroundColor: "#FFFFFF",
},
};
if (params.footer) {
attachFooterText(bubble, params.footer);
}
return bubble;
}
/**
* Create a receipt/summary card (for orders, transactions, data tables)
*
* Editorial design: Clean table layout with alternating row backgrounds,
* prominent total section, and clear visual hierarchy.
*/
export function createReceiptCard(params: {
title: string;
subtitle?: string;
items: Array<{ name: string; value: string; highlight?: boolean }>;
total?: { label: string; value: string };
footer?: string;
}): FlexBubble {
const { title, subtitle, items, total, footer } = params;
const itemRows: FlexComponent[] = items.slice(0, 12).map(
(item, index) =>
({
type: "box",
layout: "horizontal",
contents: [
{
type: "text",
text: item.name,
size: "sm",
color: item.highlight ? "#111111" : "#666666",
weight: item.highlight ? "bold" : "regular",
flex: 3,
wrap: true,
} as FlexText,
{
type: "text",
text: item.value,
size: "sm",
color: item.highlight ? "#06C755" : "#333333",
weight: item.highlight ? "bold" : "regular",
flex: 2,
align: "end",
wrap: true,
} as FlexText,
],
paddingAll: "md",
backgroundColor: index % 2 === 0 ? "#FFFFFF" : "#FAFAFA",
}) as FlexBox,
);
// Header section
const headerContents = buildTitleSubtitleHeader({ title, subtitle });
const bodyContents: FlexComponent[] = [
...buildCardHeaderSections(headerContents),
{
type: "box",
layout: "vertical",
contents: itemRows,
margin: "md",
cornerRadius: "md",
borderWidth: "light",
borderColor: "#EEEEEE",
} as FlexBox,
];
// Total section with emphasis
if (total) {
bodyContents.push({
type: "box",
layout: "horizontal",
contents: [
{
type: "text",
text: total.label,
size: "lg",
weight: "bold",
color: "#111111",
flex: 2,
} as FlexText,
{
type: "text",
text: total.value,
size: "xl",
weight: "bold",
color: "#06C755",
flex: 2,
align: "end",
} as FlexText,
],
margin: "xl",
paddingAll: "lg",
backgroundColor: "#F0FDF4",
cornerRadius: "lg",
} as FlexBox);
}
return createMegaBubbleWithFooter({ bodyContents, footer });
}
/**
* Create a calendar event card (for meetings, appointments, reminders)
*
* Editorial design: Date as hero, strong typographic hierarchy,
* color-blocked zones, full text wrapping for readability.
*/
export function createEventCard(params: {
title: string;
date: string;
time?: string;
location?: string;
description?: string;
calendar?: string;
isAllDay?: boolean;
action?: Action;
}): FlexBubble {
const { title, date, time, location, description, calendar, isAllDay, action } = params;
// Hero date block - the most important information
const dateBlock: FlexBox = {
type: "box",
layout: "vertical",
contents: [
{
type: "text",
text: date.toUpperCase(),
size: "sm",
weight: "bold",
color: "#06C755",
wrap: true,
} as FlexText,
{
type: "text",
text: isAllDay ? "ALL DAY" : (time ?? ""),
size: "xxl",
weight: "bold",
color: "#111111",
wrap: true,
margin: "xs",
} as FlexText,
],
paddingBottom: "lg",
borderWidth: "none",
};
// If no time and not all day, hide the time display
if (!time && !isAllDay) {
dateBlock.contents = [
{
type: "text",
text: date,
size: "xl",
weight: "bold",
color: "#111111",
wrap: true,
} as FlexText,
];
}
// Event title with accent bar
const titleBlock: FlexBox = {
type: "box",
layout: "horizontal",
contents: [
{
type: "box",
layout: "vertical",
contents: [],
width: "4px",
backgroundColor: "#06C755",
cornerRadius: "2px",
} as FlexBox,
{
type: "box",
layout: "vertical",
contents: [
{
type: "text",
text: title,
size: "lg",
weight: "bold",
color: "#1a1a1a",
wrap: true,
} as FlexText,
...(calendar
? [
{
type: "text",
text: calendar,
size: "xs",
color: "#888888",
margin: "sm",
wrap: true,
} as FlexText,
]
: []),
],
flex: 1,
paddingStart: "lg",
} as FlexBox,
],
paddingTop: "lg",
paddingBottom: "lg",
borderWidth: "light",
borderColor: "#EEEEEE",
};
const bodyContents: FlexComponent[] = [dateBlock, titleBlock];
// Details section (location + description) in subtle background
const hasDetails = location || description;
if (hasDetails) {
const detailItems: FlexComponent[] = [];
if (location) {
detailItems.push({
type: "box",
layout: "horizontal",
contents: [
{
type: "text",
text: "📍",
size: "sm",
flex: 0,
} as FlexText,
{
type: "text",
text: location,
size: "sm",
color: "#444444",
margin: "md",
flex: 1,
wrap: true,
} as FlexText,
],
alignItems: "flex-start",
} as FlexBox);
}
if (description) {
detailItems.push({
type: "text",
text: description,
size: "sm",
color: "#666666",
wrap: true,
margin: location ? "lg" : "none",
} as FlexText);
}
bodyContents.push({
type: "box",
layout: "vertical",
contents: detailItems,
margin: "lg",
paddingAll: "lg",
backgroundColor: "#F8F9FA",
cornerRadius: "lg",
} as FlexBox);
}
return {
type: "bubble",
size: "mega",
body: {
type: "box",
layout: "vertical",
contents: bodyContents,
paddingAll: "xl",
backgroundColor: "#FFFFFF",
action,
},
};
}
/**
* Create a calendar agenda card showing multiple events
*
* Editorial timeline design: Time-focused left column with event details
* on the right. Visual accent bars indicate event priority/recency.
*/
export function createAgendaCard(params: {
title: string;
subtitle?: string;
events: Array<{
title: string;
time?: string;
location?: string;
calendar?: string;
isNow?: boolean;
}>;
footer?: string;
}): FlexBubble {
const { title, subtitle, events, footer } = params;
// Header with title and optional subtitle
const headerContents = buildTitleSubtitleHeader({ title, subtitle });
// Event timeline items
const eventItems: FlexComponent[] = events.slice(0, 6).map((event, index) => {
const isActive = event.isNow || index === 0;
const accentColor = isActive ? "#06C755" : "#E5E5E5";
// Time column (fixed width)
const timeColumn: FlexBox = {
type: "box",
layout: "vertical",
contents: [
{
type: "text",
text: event.time ?? "—",
size: "sm",
weight: isActive ? "bold" : "regular",
color: isActive ? "#06C755" : "#666666",
align: "end",
wrap: true,
} as FlexText,
],
width: "65px",
justifyContent: "flex-start",
};
// Accent dot
const dotColumn: FlexBox = {
type: "box",
layout: "vertical",
contents: [
{
type: "box",
layout: "vertical",
contents: [],
width: "10px",
height: "10px",
backgroundColor: accentColor,
cornerRadius: "5px",
} as FlexBox,
],
width: "24px",
alignItems: "center",
justifyContent: "flex-start",
paddingTop: "xs",
};
// Event details column
const detailContents: FlexComponent[] = [
{
type: "text",
text: event.title,
size: "md",
weight: "bold",
color: "#1a1a1a",
wrap: true,
} as FlexText,
];
// Secondary info line
const secondaryParts: string[] = [];
if (event.location) {
secondaryParts.push(event.location);
}
if (event.calendar) {
secondaryParts.push(event.calendar);
}
if (secondaryParts.length > 0) {
detailContents.push({
type: "text",
text: secondaryParts.join(" · "),
size: "xs",
color: "#888888",
wrap: true,
margin: "xs",
} as FlexText);
}
const detailColumn: FlexBox = {
type: "box",
layout: "vertical",
contents: detailContents,
flex: 1,
};
return {
type: "box",
layout: "horizontal",
contents: [timeColumn, dotColumn, detailColumn],
margin: index > 0 ? "xl" : undefined,
alignItems: "flex-start",
} as FlexBox;
});
const bodyContents: FlexComponent[] = [
...buildCardHeaderSections(headerContents),
{
type: "box",
layout: "vertical",
contents: eventItems,
paddingTop: "xl",
} as FlexBox,
];
return createMegaBubbleWithFooter({ bodyContents, footer });
}