Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/server/src/gits/Layers/AutomodeProposalSweep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,13 @@ export const AutomodeProposalSweepLive = Layer.effect(
yield* Ref.set(stateRef, next);
// Sequential: hermes invocations share codex capacity — never parallelise.
const newGoals: AutomodeGoal[] = [];
let attemptedRepos = 0;
for (const repo of policy.proposalRepos) {
if (!repoAllowed(policy, repo)) {
yield* Effect.logWarning("gits.sweep.repo-not-allowed", { repo });
continue;
}
attemptedRepos += 1;
const goal = yield* sweepRepo(repo);
if (goal === null) continue;
newGoals.push(goal);
Expand All @@ -235,6 +237,21 @@ export const AutomodeProposalSweepLive = Layer.effect(
);
}
}
if (attemptedRepos > 0 && newGoals.length === 0) {
// A sweep night is consumed up front, so a silent zero-goal run means the owner
// waits a whole day without knowing anything went wrong. Always say so.
// ponytail: generic message; per-repo reasons live in the gits.sweep.* logs.
yield* notifier
.notify({
subject: "GITS nightly sweep",
text: `Sweep ran for ${attemptedRepos} repo(s) but drafted no goals. Check the cockpit logs (gits.sweep.*) for reasons.`,
})
.pipe(
Effect.catchCause((cause) =>
Effect.logWarning("gits.sweep.telegram-failed", { cause: Cause.pretty(cause) }),
),
);
}
if (newGoals.length > 0) {
const goalLines = newGoals.map(
(goal) => `${goal.title} — ${path.basename(goal.repo)} [${goal.id}]`,
Expand Down
17 changes: 16 additions & 1 deletion apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,30 @@
color: #171717;
user-select: none;
}

html.dark .gits-boot {
background: #161616;
}

html.dark .gits-boot-title {
color: #f5f5f5;
}
</style>
<title>GITS</title>
</head>
<body>
<div id="root">
<div id="boot-shell" class="gits-boot" aria-label="GITS is starting">
<img class="gits-boot-logo" src="/gits-logo-anim.gif" alt="" />
<img class="gits-boot-logo" alt="" />
<span class="gits-boot-title">GITS</span>
</div>
<script>
document.querySelector(".gits-boot-logo").src = document.documentElement.classList.contains(
"dark",
)
? "/gits-logo-anim-dark.gif"
: "/gits-logo-anim.gif";
</script>
</div>
<script type="module" src="/src/main.tsx"></script>
</body>
Expand Down
Binary file added apps/web/public/gits-logo-anim-dark.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/gits-logo-hold-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/gits-logo-hold.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 12 additions & 7 deletions apps/web/src/components/SplashScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,45 @@
import { useEffect, useState } from "react";

// One full cycle of /gits-logo-anim.gif. Elapsed time counts from the static
// One full cycle of /gits-logo-anim(-dark).gif. Elapsed time counts from the static
// index.html boot splash (window.__gitsBootStart) so the animation plays once
// total across both layers, then holds the static logo instead of looping.
// total across both layers, then holds the last frame instead of looping.
// ponytail: constant must track the gif asset; re-measure if the gif changes.
const LOGO_CYCLE_MS = 4000;
const HOLD_LOGO_SRC = "/apple-touch-icon.png";

function elapsedSinceBoot(): number {
const start = (window as Window & { __gitsBootStart?: number }).__gitsBootStart;
return typeof start === "number" ? Date.now() - start : 0;
}

export function SplashScreen() {
// Same signal the index.html boot script uses; read once — the splash is transient.
const isDark = document.documentElement.classList.contains("dark");
const [hold, setHold] = useState(() => elapsedSinceBoot() >= LOGO_CYCLE_MS);
useEffect(() => {
if (hold) return;
const timer = setTimeout(() => setHold(true), LOGO_CYCLE_MS - elapsedSinceBoot());
return () => clearTimeout(timer);
}, [hold]);
return (
<div className="flex min-h-screen flex-col items-center justify-center gap-5 bg-white">
<div className="flex min-h-screen flex-col items-center justify-center gap-5 bg-white dark:bg-[#161616]">
<div
className="relative flex size-40 items-center justify-center"
aria-label="GITS splash screen"
>
<img alt="" className="size-40 object-contain" src="/gits-logo-anim.gif" />
<img
alt=""
className="size-40 object-contain"
src={isDark ? "/gits-logo-anim-dark.gif" : "/gits-logo-anim.gif"}
/>
<img
alt=""
className={`absolute inset-0 size-40 object-contain transition-opacity duration-300 ${
hold ? "opacity-100" : "opacity-0"
}`}
src={HOLD_LOGO_SRC}
src={isDark ? "/gits-logo-hold-dark.png" : "/gits-logo-hold.png"}
/>
</div>
<span className="animate-[splash-title-in_1.1s_cubic-bezier(0.2,0.8,0.2,1)_both] font-mono text-3xl font-bold tracking-[0.42em] text-neutral-900 select-none">
<span className="animate-[splash-title-in_1.1s_cubic-bezier(0.2,0.8,0.2,1)_both] font-mono text-3xl font-bold tracking-[0.42em] text-neutral-900 select-none dark:text-neutral-100">
GITS
</span>
</div>
Expand Down
Loading