Skip to content

Commit b6e12fd

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/loop-skills
2 parents ca8fc51 + 0092213 commit b6e12fd

17 files changed

Lines changed: 567 additions & 297 deletions

apps/code/snapshots.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -569,13 +569,13 @@ snapshots:
569569
git-dialogs--sync-success--light:
570570
hash: v1.k4693efd2.8d79d77c9338aeaa73734836f0e17fde987f6d3239914f4013889d3110e5088d.Rq-w2o1uhes_a5hUWqMXeiDkfnoPCZPt932LkEQXCco
571571
loops-loopslistview--comprehensive--dark:
572-
hash: v1.k4693efd2.a9b9a8b23cca2079c3ced1878b6537ee9fbbdd0d3de205a9584fbe7d6875d405.dWfER84S8r_MU7EVDmjW8CuCf6lNhTx6RjfV_cNzKOA
572+
hash: v1.k4693efd2.3c2a6b87092674a3d395a6f5d36889ea231696d788b159f357be0926f3804c8a.Ihzlq7XIy9rRoA7DTbpLePlBhjP34o8ppgP__NXx-Yw
573573
loops-loopslistview--comprehensive--light:
574-
hash: v1.k4693efd2.9ff3f3787ef56ab9d1d39562eed6f19b6c9c1e7a6d79965fbf99fbac16ba33fb.DDuj_KZx0pcUxXwCgzNGFmCemCT1MuCzIQmbbPMgatw
574+
hash: v1.k4693efd2.42c2994def56651de08f5be20e98dbe67bfbdb03e7edd8bdf008401468c1653a.EnH7NwNkvclS7g5Htf7OCZuYEm7fI5W6hEr4DmwZC8I
575575
loops-loopslistview--long-mixed-list--dark:
576-
hash: v1.k4693efd2.683dedbfa7da3313eeaf52ff9e3cc7908af5c77173b1cad450b2acd61ac30cd4.YFm7fXVZSr29fa0R40pzq-FjRZrqIBEsIMiOiddB840
576+
hash: v1.k4693efd2.384486583f8d9b66a4bf1d9cc45036a07310cb9e1943ea5b52ff466f55a03d7b.F_sXrJ13XWHzAF8yhoKun_k4BY8Jcb3EDtT0L1I6nKw
577577
loops-loopslistview--long-mixed-list--light:
578-
hash: v1.k4693efd2.577fa23e3bc43f0ba033a59669fa73035ff2d65df1f994cf5753ac306ca830cf.r3v1hne0uC4dHpkzXriz6yzcA3fD2b8wD6ZLxA_0Fuo
578+
hash: v1.k4693efd2.d80a0c55c4490276df53f781ac88ed9d9c6ec282bc14ddb80140b3ddcc44d32f.L6hw9ZixUiyTFKOybYzmjfUSIOayvmhueMTcmYt-IWo
579579
scouts-scoutsfleetlist--filtered-to-you--dark:
580580
hash: v1.k4693efd2.a3af6e76ef36598eaa347bedc4430878ed62754660166398e0576a9978cd084b.x3Ky-O6HOw0srWdOmnnKJwFNpmGmQVWip0t7CwVaRXY
581581
scouts-scoutsfleetlist--filtered-to-you--light:

packages/ui/src/features/folder-picker/GitHubRepoPicker.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function GitHubRepoPicker({
4343
repositories,
4444
isLoading,
4545
placeholder = "Select repository...",
46+
size = "1",
4647
disabled = false,
4748
anchor,
4849
showSearchInput = true,
@@ -55,6 +56,8 @@ export function GitHubRepoPicker({
5556
hasMore: controlledHasMore,
5657
onLoadMore,
5758
}: GitHubRepoPickerProps) {
59+
const buttonSize = size === "2" ? "lg" : "sm";
60+
const buttonTextClass = size === "2" ? "text-[13px]" : "";
5861
const triggerRef = useRef<HTMLButtonElement>(null);
5962
const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
6063
const [uncontrolledSearchQuery, setUncontrolledSearchQuery] = useState("");
@@ -91,7 +94,12 @@ export function GitHubRepoPicker({
9194

9295
if (isLoading && !showInlineLoadingState) {
9396
return (
94-
<Button variant="outline" disabled size="sm">
97+
<Button
98+
variant="outline"
99+
disabled
100+
size={buttonSize}
101+
className={buttonTextClass}
102+
>
95103
<GithubLogo size={16} weight="regular" className="shrink-0" />
96104
Loading repos...
97105
</Button>
@@ -107,7 +115,12 @@ export function GitHubRepoPicker({
107115
!hasActiveRemoteSearch
108116
) {
109117
return (
110-
<Button variant="outline" disabled size="sm">
118+
<Button
119+
variant="outline"
120+
disabled
121+
size={buttonSize}
122+
className={buttonTextClass}
123+
>
111124
<GithubLogo size={16} weight="regular" className="shrink-0" />
112125
No GitHub repos
113126
</Button>
@@ -121,10 +134,10 @@ export function GitHubRepoPicker({
121134
<Button
122135
type="button"
123136
variant="outline"
124-
size="sm"
137+
size={buttonSize}
125138
disabled
126139
aria-label="Repository"
127-
className="pointer-events-none min-w-0 max-w-full cursor-default justify-start disabled:opacity-100"
140+
className={`pointer-events-none min-w-0 max-w-full cursor-default justify-start disabled:opacity-100 ${buttonTextClass}`}
128141
>
129142
<GithubLogo size={14} weight="regular" className="shrink-0" />
130143
<span className="min-w-0 truncate">{onlyRepo}</span>
@@ -165,9 +178,10 @@ export function GitHubRepoPicker({
165178
<Button
166179
ref={triggerRef}
167180
variant="outline"
168-
size="sm"
181+
size={buttonSize}
169182
disabled={disabled}
170183
aria-label="Repository"
184+
className={buttonTextClass}
171185
>
172186
<GithubLogo size={14} weight="regular" className="shrink-0" />
173187
<span className="min-w-0 truncate">{value ?? placeholder}</span>

packages/ui/src/features/loops/components/LoopBehaviorFields.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function LoopBehaviorFields({
1818
<Flex
1919
direction="column"
2020
gap="2"
21-
className="rounded-(--radius-2) border border-border bg-(--color-panel-solid) p-3"
21+
className="rounded-(--radius-2) border border-border bg-(--gray-1) p-3"
2222
>
2323
<Flex align="center" justify="between" gap="2">
2424
<Flex direction="column" gap="0">

packages/ui/src/features/loops/components/LoopContextFields.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function LoopContextFields({
5050
};
5151

5252
const contextOptions = [
53-
{ value: NOT_ATTACHED_VALUE, label: "Not attached" },
53+
{ value: NOT_ATTACHED_VALUE, label: "Not attached to a channel" },
5454
...channels.map((channel) => ({
5555
value: channel.id,
5656
label: `#${channel.name}`,
@@ -63,15 +63,16 @@ export function LoopContextFields({
6363
value={value?.folderId ?? NOT_ATTACHED_VALUE}
6464
options={contextOptions}
6565
disabled={disabled}
66-
ariaLabel="Context"
66+
size="lg"
67+
ariaLabel="Context channel"
6768
onValueChange={selectContext}
6869
/>
6970

7071
{value ? (
7172
<Flex
7273
direction="column"
7374
gap="3"
74-
className="rounded-(--radius-2) border border-border bg-(--color-panel-solid) p-3"
75+
className="rounded-(--radius-2) border border-border bg-(--gray-1) p-3"
7576
>
7677
<ToggleRow
7778
title="Show runs in the feed"
@@ -110,6 +111,7 @@ export function LoopContextFields({
110111
label: dashboard.name,
111112
}))}
112113
disabled={disabled}
114+
size="lg"
113115
ariaLabel="Canvas"
114116
onValueChange={(canvasId) =>
115117
patchOutputs({ canvas_id: canvasId })

packages/ui/src/features/loops/components/LoopDetailView.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export function LoopDetailView({ loopId }: { loopId: string }) {
112112

113113
if (isLoading) {
114114
return (
115-
<div className="mx-auto w-full max-w-5xl px-8 py-6">
115+
<div className="mx-auto w-full max-w-5xl px-8 py-8">
116116
<div className="h-24 animate-pulse rounded-(--radius-2) border border-border bg-(--gray-2)" />
117117
</div>
118118
);
@@ -123,16 +123,20 @@ export function LoopDetailView({ loopId }: { loopId: string }) {
123123
}
124124

125125
return (
126-
<div className="mx-auto w-full max-w-5xl px-8 py-6">
127-
<Flex direction="column" gap="5">
126+
<div className="h-full min-h-0 overflow-y-auto">
127+
<Flex
128+
direction="column"
129+
gap="5"
130+
className="mx-auto w-full max-w-5xl px-8 py-8"
131+
>
128132
<Flex direction="column" gap="3">
129133
<Button
130134
variant="link-muted"
131-
size="xs"
135+
size="sm"
132136
onClick={navigateToLoops}
133137
className="w-fit px-0"
134138
>
135-
<ArrowLeftIcon size={13} />
139+
<ArrowLeftIcon size={15} />
136140
Loops
137141
</Button>
138142

@@ -155,7 +159,7 @@ export function LoopDetailView({ loopId }: { loopId: string }) {
155159
/>
156160
<Button
157161
variant="outline"
158-
size="xs"
162+
size="sm"
159163
loading={runLoop.isPending}
160164
disabled={runLoop.isPending}
161165
onClick={handleRunNow}
@@ -164,14 +168,14 @@ export function LoopDetailView({ loopId }: { loopId: string }) {
164168
</Button>
165169
<Button
166170
variant="outline"
167-
size="xs"
171+
size="sm"
168172
onClick={() => navigateToEditLoop(loop.id)}
169173
>
170174
Edit
171175
</Button>
172176
<Button
173177
variant="destructive"
174-
size="xs"
178+
size="sm"
175179
onClick={() => setDeleteOpen(true)}
176180
>
177181
Delete
@@ -219,7 +223,11 @@ export function LoopDetailView({ loopId }: { loopId: string }) {
219223
) : (
220224
<Flex direction="column" gap="2">
221225
{runs.map((run) => (
222-
<LoopRunRow key={run.id} run={run} />
226+
<LoopRunRow
227+
key={run.id}
228+
run={run}
229+
onStopped={() => void runsQuery.refetch()}
230+
/>
223231
))}
224232
</Flex>
225233
)}
@@ -498,7 +506,7 @@ function InstructionsSection({ loop }: { loop: LoopSchemas.Loop }) {
498506
value={draft ?? loop.instructions}
499507
disabled={updateLoop.isPending}
500508
aria-label="Loop instructions"
501-
className="min-h-[200px] bg-(--color-panel-solid) text-[12.5px] leading-relaxed"
509+
className="max-h-[400px] min-h-[200px] bg-(--color-panel-solid) text-[12.5px] leading-relaxed"
502510
onChange={(e) => setDraft(e.currentTarget.value)}
503511
onBlur={(e) => commit(e.currentTarget.value)}
504512
onKeyDown={(e) => {

packages/ui/src/features/loops/components/LoopForm.tsx

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import {
55
Check,
66
} from "@phosphor-icons/react";
77
import { type LoopSchemas, LoopsApiError } from "@posthog/api-client/loops";
8+
import { PROJECT_BLUEBIRD_FLAG } from "@posthog/shared";
9+
import { useFeatureFlag } from "@posthog/ui/features/feature-flags/useFeatureFlag";
810
import { SettingsOptionSelect } from "@posthog/ui/features/settings/SettingsOptionSelect";
11+
import { useSidebarStore } from "@posthog/ui/features/sidebar/sidebarStore";
912
import { useSetHeaderContent } from "@posthog/ui/hooks/useSetHeaderContent";
1013
import { Button } from "@posthog/ui/primitives/Button";
1114
import { toast } from "@posthog/ui/primitives/toast";
@@ -93,6 +96,17 @@ export function LoopForm({ loop }: LoopFormProps) {
9396
if (!loop) useLoopDraftStore.getState().setPrefill(null);
9497
}, [loop]);
9598

99+
// Contexts are a channels surface; hide the attachment UI when channels are
100+
// off, unless this loop is already attached so the link stays visible and
101+
// detachable.
102+
const bluebirdEnabled = useFeatureFlag(
103+
PROJECT_BLUEBIRD_FLAG,
104+
import.meta.env.DEV,
105+
);
106+
const channelsEnabled =
107+
useSidebarStore((s) => s.channelsEnabled) && bluebirdEnabled;
108+
const showContextField = channelsEnabled || !!values.contextTarget;
109+
96110
const createLoop = useCreateLoop();
97111
const updateLoop = useUpdateLoop(loop?.id ?? "");
98112
const deleteLoop = useDeleteLoop();
@@ -229,13 +243,13 @@ export function LoopForm({ loop }: LoopFormProps) {
229243
<Box className="flex h-full items-center justify-center p-6">
230244
<Flex
231245
direction="column"
232-
className="max-h-full w-full max-w-[600px] overflow-hidden rounded-(--radius-3) border border-border bg-(--color-panel-solid) shadow-xl"
246+
className="max-h-full w-full max-w-[640px] overflow-hidden rounded-(--radius-3) border border-border bg-(--color-panel-solid) shadow-xl"
233247
>
234-
<Box className="border-border border-b px-5 pt-5 pb-4">
248+
<Box className="border-border border-b px-6 pt-5 pb-4">
235249
<Stepper current={step} complete={stepComplete} onSelect={setStep} />
236250
</Box>
237251

238-
<Box className="min-h-0 flex-1 overflow-y-auto px-5 py-5">
252+
<Box className="min-h-0 flex-1 overflow-y-auto px-6 py-6">
239253
{step === 0 ? (
240254
<Step
241255
title="What should this loop do?"
@@ -271,7 +285,7 @@ export function LoopForm({ loop }: LoopFormProps) {
271285
{step === 1 ? (
272286
<Step
273287
title="When should it run?"
274-
description="Add one or more triggers. Leave empty to run it only on demand."
288+
description="A loop can have several triggers, and any one of them starts a run. With no triggers, you run it yourself from the loop's page."
275289
>
276290
<LoopTriggerEditor
277291
triggers={values.triggers}
@@ -292,14 +306,15 @@ export function LoopForm({ loop }: LoopFormProps) {
292306
className="max-w-[340px]"
293307
hint={
294308
values.contextTarget
295-
? "Loops attached to a context post runs to its shared feed, so they're visible to everyone on the project."
309+
? "Loops attached to a channel post runs to its shared feed, so they're visible to everyone on the project."
296310
: undefined
297311
}
298312
>
299313
<SettingsOptionSelect
300314
value={values.visibility}
301315
options={VISIBILITY_OPTIONS}
302316
disabled={isSubmitting || !!values.contextTarget}
317+
size="lg"
303318
ariaLabel="Visibility"
304319
onValueChange={(value) =>
305320
patch({
@@ -311,35 +326,39 @@ export function LoopForm({ loop }: LoopFormProps) {
311326

312327
<Divider />
313328

314-
<Field
315-
label="Context"
316-
hint="Attach this loop to a context to file its runs in the feed and keep its context.md or a canvas up to date."
317-
>
318-
<LoopContextFields
319-
value={values.contextTarget}
320-
disabled={isSubmitting}
321-
onChange={(contextTarget) =>
322-
patch(
323-
contextTarget
324-
? { contextTarget, visibility: "team" }
325-
: { contextTarget },
326-
)
327-
}
328-
/>
329-
</Field>
329+
{showContextField ? (
330+
<>
331+
<Field
332+
label="Context"
333+
hint="A context is one of the channels in your sidebar. Attach this loop to a channel and its runs show up in that channel's feed; it can also keep the channel's context.md or a canvas up to date."
334+
>
335+
<LoopContextFields
336+
value={values.contextTarget}
337+
disabled={isSubmitting}
338+
onChange={(contextTarget) =>
339+
patch(
340+
contextTarget
341+
? { contextTarget, visibility: "team" }
342+
: { contextTarget },
343+
)
344+
}
345+
/>
346+
</Field>
330347

331-
<Divider />
348+
<Divider />
349+
</>
350+
) : null}
332351

333352
<Field
334-
label="Repository"
353+
label="Base repository"
335354
hint={
336355
values.repositories.length > 1
337356
? `${values.repositories.length - 1} more ${
338357
values.repositories.length === 2
339358
? "repository stays"
340359
: "repositories stay"
341360
} attached to this loop.`
342-
: "Optional. Leave empty for a report-only loop that works purely through connectors."
361+
: "The repository runs check out and work in. Optional. Leave empty for a report-only loop that works purely through connectors."
343362
}
344363
>
345364
<LoopRepositoryPicker
@@ -420,7 +439,7 @@ export function LoopForm({ loop }: LoopFormProps) {
420439
title="Review"
421440
description="Check everything before you create the loop."
422441
>
423-
<ReviewList values={values} />
442+
<ReviewList values={values} showContext={showContextField} />
424443
</Step>
425444
) : null}
426445
</Box>
@@ -565,7 +584,13 @@ function Divider() {
565584
return <Box className="h-px bg-(--gray-4)" />;
566585
}
567586

568-
function ReviewList({ values }: { values: LoopFormValues }) {
587+
function ReviewList({
588+
values,
589+
showContext,
590+
}: {
591+
values: LoopFormValues;
592+
showContext: boolean;
593+
}) {
569594
const reasoning = values.reasoningEffort ?? "auto";
570595
const channels = (["push", "email", "slack"] as const).filter(
571596
(channel) => values.notifications[channel]?.enabled,
@@ -596,12 +621,14 @@ function ReviewList({ values }: { values: LoopFormValues }) {
596621
values.model || "Default model"
597622
} · ${reasoning} reasoning`}
598623
/>
624+
{showContext ? (
625+
<ReviewRow
626+
label="Context"
627+
value={describeContext(values.contextTarget)}
628+
/>
629+
) : null}
599630
<ReviewRow
600-
label="Context"
601-
value={describeContext(values.contextTarget)}
602-
/>
603-
<ReviewRow
604-
label="Repository"
631+
label="Base repository"
605632
value={
606633
values.repositories.length > 0
607634
? values.repositories.map((repo) => repo.full_name).join(", ")
@@ -652,7 +679,7 @@ function ReviewRow({
652679
}
653680

654681
function describeContext(target: LoopContextTargetDraft | null): string {
655-
if (!target) return "Not attached";
682+
if (!target) return "Not attached to a channel";
656683
const outputs: string[] = [];
657684
if (target.outputs.post_to_feed) outputs.push("feed");
658685
if (target.outputs.update_context) outputs.push("context.md");

0 commit comments

Comments
 (0)