Skip to content

Commit c30ba10

Browse files
committed
chore: prepare for release
1 parent b8df30b commit c30ba10

11 files changed

Lines changed: 160 additions & 53 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
node_modules
22
dist
33
build
4+
artifacts
45
.DS_Store
56
*.log
67
.env
-18.6 MB
Binary file not shown.
-20.7 MB
Binary file not shown.

artifacts/stable-macos-arm64-update.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

electrobun.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import type { ElectrobunConfig } from "electrobun";
2+
import { readFileSync } from "node:fs";
23

34
const releaseBaseUrl = process.env["RELEASE_BASE_URL"] || "";
45
const enableCodesign = process.env["ELECTROBUN_ENABLE_CODESIGN"] === "true";
56
const enableNotarize = process.env["ELECTROBUN_ENABLE_NOTARIZE"] === "true";
7+
const packageVersion = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8")).version;
68

79
export default {
810
app: {
911
name: "Loopndroll",
1012
identifier: "dev.loopndroll.app",
11-
version: "0.1.0",
13+
version: packageVersion,
1214
},
1315
release: {
1416
baseUrl: releaseBaseUrl,

scripts/release-macos.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ set -euo pipefail
55
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
66
cd "$ROOT_DIR"
77

8+
if [[ -f ".env" ]]; then
9+
set -a
10+
# Load local release credentials and defaults for macOS packaging.
11+
source ".env"
12+
set +a
13+
fi
14+
815
fail() {
916
printf 'error: %s\n' "$1" >&2
1017
exit 1

src/bun/loopndroll.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,7 @@ function shouldEnableHookDebugLogging() {
184184
return isTruthyEnvValue(process.env[HOOK_DEBUG_LOG_ENV_NAME]);
185185
}
186186

187-
function sanitizeHookDebugLogValue(
188-
value: unknown,
189-
seen = new WeakSet<object>(),
190-
): unknown {
187+
function sanitizeHookDebugLogValue(value: unknown, seen = new WeakSet<object>()): unknown {
191188
if (value == null || typeof value === "boolean" || typeof value === "number") {
192189
return value;
193190
}
@@ -1384,6 +1381,19 @@ function isPersistentPromptPreset(preset: LoopPreset | null) {
13841381
);
13851382
}
13861383

1384+
const OPT_OUT_EXISTING_INACTIVE_SESSIONS_FROM_GLOBAL_PRESET_SQL = `update sessions
1385+
set preset_overridden = 1
1386+
where archived = 0
1387+
and preset is null
1388+
and preset_overridden = 0
1389+
and active_since is null`;
1390+
1391+
function optOutExistingInactiveSessionsFromGlobalPreset(executor: {
1392+
run: (sql: string) => unknown;
1393+
}) {
1394+
executor.run(OPT_OUT_EXISTING_INACTIVE_SESSIONS_FROM_GLOBAL_PRESET_SQL);
1395+
}
1396+
13871397
function getEffectivePresetForSession(db: Database, sessionId: string) {
13881398
const row = db
13891399
.query(
@@ -1605,8 +1615,11 @@ function updateSessionPresetFromBridge(db: Database, sessionId: string, preset:
16051615
}
16061616

16071617
function updateGlobalPresetFromBridge(db: Database, preset: LoopPreset | null) {
1608-
const timestamp = nowIsoString();
16091618
const applyUpdate = db.transaction(() => {
1619+
if (preset !== null) {
1620+
optOutExistingInactiveSessionsFromGlobalPreset(db);
1621+
}
1622+
16101623
db.query("update settings set global_preset = ? where id = 1").run(preset);
16111624

16121625
if (preset === null) {
@@ -1616,13 +1629,6 @@ function updateGlobalPresetFromBridge(db: Database, preset: LoopPreset | null) {
16161629
where archived = 0
16171630
and preset_overridden = 0`,
16181631
);
1619-
} else {
1620-
db.run(
1621-
`update sessions
1622-
set active_since = coalesce(active_since, '${timestamp}')
1623-
where archived = 0
1624-
and preset_overridden = 0`,
1625-
);
16261632
}
16271633

16281634
db.query("delete from session_runtime").run();
@@ -4122,9 +4128,12 @@ export async function setLoopScope(scope: LoopScope) {
41224128
export async function setGlobalPreset(preset: LoopPreset | null) {
41234129
const paths = getLoopndrollPaths();
41244130
const { db } = getLoopndrollDatabase(paths.databasePath);
4125-
const timestamp = nowIsoString();
41264131

41274132
db.transaction((tx) => {
4133+
if (preset !== null) {
4134+
optOutExistingInactiveSessionsFromGlobalPreset(tx);
4135+
}
4136+
41284137
tx.update(settings).set({ globalPreset: preset }).where(eq(settings.id, 1)).run();
41294138
if (preset === null) {
41304139
tx.run(
@@ -4133,13 +4142,6 @@ export async function setGlobalPreset(preset: LoopPreset | null) {
41334142
where archived = 0
41344143
and preset_overridden = 0`,
41354144
);
4136-
} else {
4137-
tx.run(
4138-
`update sessions
4139-
set active_since = coalesce(active_since, '${timestamp}')
4140-
where archived = 0
4141-
and preset_overridden = 0`,
4142-
);
41434145
}
41444146
tx.delete(sessionRuntime).run();
41454147
if (preset !== "await-reply") {

src/components/chat-card.tsx

Lines changed: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { CSSProperties, ReactNode } from "react";
1+
import type { ReactNode } from "react";
22
import {
33
ChatCircleDots,
44
Checks,
@@ -10,12 +10,75 @@ import { cn } from "@/lib/utils";
1010
import { Button } from "@/components/ui/button";
1111
import { Card, CardContent, CardFooter, CardTitle } from "@/components/ui/card";
1212
import { Skeleton } from "@/components/ui/skeleton";
13+
import type { LoopPreset } from "@/lib/loopndroll";
14+
15+
export type ChatCardTheme = "orange" | "cyan" | "emerald" | "olive";
16+
17+
export function getChatCardThemeForPreset(preset: LoopPreset | null | undefined): ChatCardTheme {
18+
if (preset === "await-reply") {
19+
return "cyan";
20+
}
21+
22+
if (preset === "completion-checks") {
23+
return "emerald";
24+
}
25+
26+
if (preset?.startsWith("max-turns-")) {
27+
return "olive";
28+
}
29+
30+
return "orange";
31+
}
32+
33+
const CHAT_CARD_THEME_CLASSES: Record<
34+
ChatCardTheme,
35+
{
36+
card: string;
37+
marker: string;
38+
footer: string;
39+
footerText: string;
40+
button: string;
41+
}
42+
> = {
43+
orange: {
44+
card: "border-orange-900/20 bg-orange-300 text-orange-950 shadow-none",
45+
marker: "border-orange-300/20 bg-orange-950/80 text-orange-300",
46+
footer: "border-t-orange-900/15 bg-orange-400/50",
47+
footerText: "text-orange-950",
48+
button:
49+
"border-orange-950 bg-orange-950 text-orange-200 shadow-none hover:bg-orange-900 hover:text-orange-200 active:scale-[0.98] dark:border-orange-950 dark:bg-orange-950 dark:hover:bg-orange-900 dark:text-orange-200",
50+
},
51+
cyan: {
52+
card: "border-cyan-900/20 bg-cyan-300 text-cyan-950 shadow-none",
53+
marker: "border-cyan-300/20 bg-cyan-950/80 text-cyan-300",
54+
footer: "border-t-cyan-900/15 bg-cyan-400/50",
55+
footerText: "text-cyan-950",
56+
button:
57+
"border-cyan-950 bg-cyan-950 text-cyan-200 shadow-none hover:bg-cyan-900 hover:text-cyan-200 active:scale-[0.98] dark:border-cyan-950 dark:bg-cyan-950 dark:hover:bg-cyan-900 dark:text-cyan-200",
58+
},
59+
emerald: {
60+
card: "border-emerald-900/20 bg-emerald-300 text-emerald-950 shadow-none",
61+
marker: "border-emerald-300/20 bg-emerald-950/80 text-emerald-300",
62+
footer: "border-t-emerald-900/15 bg-emerald-400/50",
63+
footerText: "text-emerald-950",
64+
button:
65+
"border-emerald-950 bg-emerald-950 text-emerald-200 shadow-none hover:bg-emerald-900 hover:text-emerald-200 active:scale-[0.98] dark:border-emerald-950 dark:bg-emerald-950 dark:hover:bg-emerald-900 dark:text-emerald-200",
66+
},
67+
olive: {
68+
card: "border-olive-900/20 bg-olive-300 text-olive-950 shadow-none",
69+
marker: "border-olive-300/20 bg-olive-950/80 text-olive-300",
70+
footer: "border-t-olive-900/15 bg-olive-400/50",
71+
footerText: "text-olive-950",
72+
button:
73+
"border-olive-950 bg-olive-950 text-olive-200 shadow-none hover:bg-olive-900 hover:text-olive-200 active:scale-[0.98] dark:border-olive-950 dark:bg-olive-950 dark:hover:bg-olive-900 dark:text-olive-200",
74+
},
75+
};
1376

1477
type ChatCardProps = {
1578
title?: ReactNode;
1679
marker?: ReactNode;
1780
markerContainerClassName?: string;
18-
tone?: string;
81+
theme?: ChatCardTheme;
1982
isRunning?: boolean;
2083
actionLabel?: string;
2184
onAction?: () => void;
@@ -32,7 +95,7 @@ export function ChatCard({
3295
title,
3396
marker,
3497
markerContainerClassName,
35-
tone,
98+
theme,
3699
isRunning = false,
37100
actionLabel = "Start",
38101
onAction,
@@ -45,32 +108,18 @@ export function ChatCard({
45108
footerStart,
46109
}: ChatCardProps) {
47110
const placeholder = loading || empty;
48-
const tinted = Boolean(tone);
49-
const cardStyle: CSSProperties | undefined = tinted
50-
? {
51-
backgroundColor: tone,
52-
color: "oklch(0.205 0 0)",
53-
borderColor: `color-mix(in oklch, ${tone} 78%, black)`,
54-
}
55-
: undefined;
56-
const footerStyle: CSSProperties | undefined = tinted
57-
? {
58-
backgroundColor: `color-mix(in oklch, ${tone} 80%, black)`,
59-
borderTopColor: `color-mix(in oklch, ${tone} 72%, black)`,
60-
}
61-
: undefined;
111+
const themedClasses = theme ? CHAT_CARD_THEME_CLASSES[theme] : null;
62112

63113
return (
64114
<Card
65115
aria-busy={loading || undefined}
66116
aria-hidden={empty || undefined}
67117
className={cn(
68118
"size-80 shrink-0 snap-start pb-0",
69-
isRunning && "outline outline-1 -outline-offset-1 outline-green-500/50",
119+
themedClasses?.card,
70120
placeholder && "relative gap-0 overflow-hidden py-0",
71121
className,
72122
)}
73-
style={cardStyle}
74123
>
75124
<CardContent
76125
className={cn("flex flex-1 items-start", placeholder && "p-0", contentClassName)}
@@ -81,9 +130,9 @@ export function ChatCard({
81130
<div className="flex flex-col items-start gap-5">
82131
<div
83132
className={cn(
84-
"flex size-12 items-center justify-center rounded-full",
85-
tinted
86-
? "border border-black/10 bg-black/6 text-inherit"
133+
"flex size-12 items-center justify-center rounded-md",
134+
themedClasses
135+
? themedClasses.marker
87136
: "border border-white/10 bg-white/4 text-foreground",
88137
markerContainerClassName,
89138
)}
@@ -103,17 +152,19 @@ export function ChatCard({
103152
<CardFooter
104153
className={cn(
105154
"mt-auto justify-between gap-3 border-t bg-muted/50 px-4 pb-4 [.border-t]:pt-4",
155+
themedClasses?.footer,
106156
footerClassName,
107157
)}
108-
style={footerStyle}
109158
>
110-
<div className="min-h-8 min-w-0">{footerStart}</div>
159+
<div className={cn("min-h-8 min-w-0", themedClasses?.footerText)}>
160+
{footerStart}
161+
</div>
111162
<Button
112163
aria-pressed={isRunning}
113164
onClick={onAction}
114165
className={cn(
115166
"w-20 gap-1.5",
116-
tinted && "border-black/10 bg-white/35 text-black shadow-none hover:bg-white/45",
167+
themedClasses?.button,
117168
)}
118169
size="sm"
119170
type="button"

src/components/chat-status-indicator.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
11
import { cn } from "@/lib/utils";
2+
import type { ChatCardTheme } from "@/components/chat-card";
23

34
type ChatStatusIndicatorProps = {
45
active?: boolean;
6+
theme?: ChatCardTheme;
57
className?: string;
68
};
79

8-
export function ChatStatusIndicator({ active = false, className }: ChatStatusIndicatorProps) {
10+
const ACTIVE_THEME_CLASSES: Record<ChatCardTheme, string> = {
11+
orange: "text-orange-500",
12+
cyan: "text-cyan-500",
13+
emerald: "text-emerald-500",
14+
olive: "text-olive-500",
15+
};
16+
17+
export function ChatStatusIndicator({
18+
active = false,
19+
theme = "orange",
20+
className,
21+
}: ChatStatusIndicatorProps) {
922
return (
1023
<span
1124
aria-hidden="true"
1225
className={cn(
1326
"chat-status-indicator",
1427
active
15-
? "chat-status-indicator--fillsweep text-green-500"
28+
? cn("chat-status-indicator--fillsweep", ACTIVE_THEME_CLASSES[theme])
1629
: "chat-status-indicator--diagsweep text-foreground/20",
1730
className,
1831
)}

0 commit comments

Comments
 (0)