mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-20 21:23:23 +00:00
Dreaming: update multiphase stats and UI polish
This commit is contained in:
@@ -119,6 +119,8 @@ describe("doctor.memory.status", () => {
|
||||
dreaming: expect.objectContaining({
|
||||
enabled: true,
|
||||
shortTermCount: 0,
|
||||
totalSignalCount: 0,
|
||||
phaseSignalCount: 0,
|
||||
promotedTotal: 0,
|
||||
promotedToday: 0,
|
||||
phases: expect.objectContaining({
|
||||
@@ -183,6 +185,18 @@ describe("doctor.memory.status", () => {
|
||||
".dreams",
|
||||
"short-term-recall.json",
|
||||
);
|
||||
const mainPhaseSignalPath = path.join(
|
||||
mainWorkspaceDir,
|
||||
"memory",
|
||||
".dreams",
|
||||
"phase-signals.json",
|
||||
);
|
||||
const alphaPhaseSignalPath = path.join(
|
||||
alphaWorkspaceDir,
|
||||
"memory",
|
||||
".dreams",
|
||||
"phase-signals.json",
|
||||
);
|
||||
await fs.mkdir(path.dirname(mainStorePath), { recursive: true });
|
||||
await fs.mkdir(path.dirname(alphaStorePath), { recursive: true });
|
||||
await fs.writeFile(
|
||||
@@ -195,11 +209,15 @@ describe("doctor.memory.status", () => {
|
||||
"memory:memory/2026-04-03.md:1:2": {
|
||||
path: "memory/2026-04-03.md",
|
||||
source: "memory",
|
||||
recallCount: 2,
|
||||
dailyCount: 1,
|
||||
promotedAt: undefined,
|
||||
},
|
||||
"memory:memory/2026-04-02.md:1:2": {
|
||||
path: "memory/2026-04-02.md",
|
||||
source: "memory",
|
||||
recallCount: 9,
|
||||
dailyCount: 5,
|
||||
promotedAt: recentIso,
|
||||
},
|
||||
},
|
||||
@@ -219,11 +237,15 @@ describe("doctor.memory.status", () => {
|
||||
"memory:memory/2026-04-01.md:1:2": {
|
||||
path: "memory/2026-04-01.md",
|
||||
source: "memory",
|
||||
recallCount: 7,
|
||||
dailyCount: 4,
|
||||
promotedAt: olderIso,
|
||||
},
|
||||
"memory:memory/2026-04-04.md:1:2": {
|
||||
path: "memory/2026-04-04.md",
|
||||
source: "memory",
|
||||
recallCount: 8,
|
||||
dailyCount: 3,
|
||||
promotedAt: recentIso,
|
||||
},
|
||||
},
|
||||
@@ -233,6 +255,46 @@ describe("doctor.memory.status", () => {
|
||||
)}\n`,
|
||||
"utf-8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
mainPhaseSignalPath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
updatedAt: recentIso,
|
||||
entries: {
|
||||
"memory:memory/2026-04-03.md:1:2": {
|
||||
lightHits: 2,
|
||||
remHits: 3,
|
||||
},
|
||||
"memory:memory/2026-04-02.md:1:2": {
|
||||
lightHits: 9,
|
||||
remHits: 9,
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf-8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
alphaPhaseSignalPath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
updatedAt: recentIso,
|
||||
entries: {
|
||||
"memory:memory/2026-04-01.md:1:2": {
|
||||
lightHits: 5,
|
||||
remHits: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
agents: {
|
||||
@@ -308,6 +370,12 @@ describe("doctor.memory.status", () => {
|
||||
enabled: true,
|
||||
timezone: "America/Los_Angeles",
|
||||
shortTermCount: 1,
|
||||
recallSignalCount: 2,
|
||||
dailySignalCount: 1,
|
||||
totalSignalCount: 3,
|
||||
phaseSignalCount: 5,
|
||||
lightPhaseHitCount: 2,
|
||||
remPhaseHitCount: 3,
|
||||
promotedTotal: 3,
|
||||
promotedToday: 2,
|
||||
phases: expect.objectContaining({
|
||||
|
||||
@@ -17,6 +17,7 @@ import { formatError } from "../server-utils.js";
|
||||
import type { GatewayRequestHandlers } from "./types.js";
|
||||
|
||||
const SHORT_TERM_STORE_RELATIVE_PATH = path.join("memory", ".dreams", "short-term-recall.json");
|
||||
const SHORT_TERM_PHASE_SIGNAL_RELATIVE_PATH = path.join("memory", ".dreams", "phase-signals.json");
|
||||
const MANAGED_DEEP_SLEEP_CRON_NAME = "Memory Dreaming Promotion";
|
||||
const MANAGED_DEEP_SLEEP_CRON_TAG = "[managed-by=memory-core.short-term-promotion]";
|
||||
const DEEP_SLEEP_SYSTEM_EVENT_TEXT = "__openclaw_memory_core_short_term_promotion_dream__";
|
||||
@@ -56,11 +57,19 @@ type DoctorMemoryDreamingPayload = {
|
||||
storageMode: "inline" | "separate" | "both";
|
||||
separateReports: boolean;
|
||||
shortTermCount: number;
|
||||
recallSignalCount: number;
|
||||
dailySignalCount: number;
|
||||
totalSignalCount: number;
|
||||
phaseSignalCount: number;
|
||||
lightPhaseHitCount: number;
|
||||
remPhaseHitCount: number;
|
||||
promotedTotal: number;
|
||||
promotedToday: number;
|
||||
storePath?: string;
|
||||
phaseSignalPath?: string;
|
||||
lastPromotedAt?: string;
|
||||
storeError?: string;
|
||||
phaseSignalError?: string;
|
||||
phases: {
|
||||
light: DoctorMemoryLightDreamingPayload;
|
||||
deep: DoctorMemoryDeepDreamingPayload;
|
||||
@@ -106,11 +115,19 @@ function resolveDreamingConfig(
|
||||
): Omit<
|
||||
DoctorMemoryDreamingPayload,
|
||||
| "shortTermCount"
|
||||
| "recallSignalCount"
|
||||
| "dailySignalCount"
|
||||
| "totalSignalCount"
|
||||
| "phaseSignalCount"
|
||||
| "lightPhaseHitCount"
|
||||
| "remPhaseHitCount"
|
||||
| "promotedTotal"
|
||||
| "promotedToday"
|
||||
| "storePath"
|
||||
| "phaseSignalPath"
|
||||
| "lastPromotedAt"
|
||||
| "storeError"
|
||||
| "phaseSignalError"
|
||||
> {
|
||||
const resolved = resolveMemoryDreamingConfig({
|
||||
pluginConfig: resolveMemoryCorePluginConfig(cfg),
|
||||
@@ -180,31 +197,55 @@ function isShortTermMemoryPath(filePath: string): boolean {
|
||||
type DreamingStoreStats = Pick<
|
||||
DoctorMemoryDreamingPayload,
|
||||
| "shortTermCount"
|
||||
| "recallSignalCount"
|
||||
| "dailySignalCount"
|
||||
| "totalSignalCount"
|
||||
| "phaseSignalCount"
|
||||
| "lightPhaseHitCount"
|
||||
| "remPhaseHitCount"
|
||||
| "promotedTotal"
|
||||
| "promotedToday"
|
||||
| "storePath"
|
||||
| "phaseSignalPath"
|
||||
| "lastPromotedAt"
|
||||
| "storeError"
|
||||
| "phaseSignalError"
|
||||
>;
|
||||
|
||||
function toNonNegativeInt(value: unknown): number {
|
||||
const num = Number(value);
|
||||
if (!Number.isFinite(num)) {
|
||||
return 0;
|
||||
}
|
||||
return Math.max(0, Math.floor(num));
|
||||
}
|
||||
|
||||
async function loadDreamingStoreStats(
|
||||
workspaceDir: string,
|
||||
nowMs: number,
|
||||
timezone?: string,
|
||||
): Promise<DreamingStoreStats> {
|
||||
const storePath = path.join(workspaceDir, SHORT_TERM_STORE_RELATIVE_PATH);
|
||||
const phaseSignalPath = path.join(workspaceDir, SHORT_TERM_PHASE_SIGNAL_RELATIVE_PATH);
|
||||
try {
|
||||
const raw = await fs.readFile(storePath, "utf-8");
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
const store = asRecord(parsed);
|
||||
const entries = asRecord(store?.entries) ?? {};
|
||||
let shortTermCount = 0;
|
||||
let recallSignalCount = 0;
|
||||
let dailySignalCount = 0;
|
||||
let totalSignalCount = 0;
|
||||
let phaseSignalCount = 0;
|
||||
let lightPhaseHitCount = 0;
|
||||
let remPhaseHitCount = 0;
|
||||
let promotedTotal = 0;
|
||||
let promotedToday = 0;
|
||||
let latestPromotedAtMs = Number.NEGATIVE_INFINITY;
|
||||
let latestPromotedAt: string | undefined;
|
||||
const activeKeys = new Set<string>();
|
||||
|
||||
for (const value of Object.values(entries)) {
|
||||
for (const [entryKey, value] of Object.entries(entries)) {
|
||||
const entry = asRecord(value);
|
||||
if (!entry) {
|
||||
continue;
|
||||
@@ -217,6 +258,12 @@ async function loadDreamingStoreStats(
|
||||
const promotedAt = normalizeTrimmedString(entry.promotedAt);
|
||||
if (!promotedAt) {
|
||||
shortTermCount += 1;
|
||||
activeKeys.add(entryKey);
|
||||
const recallCount = toNonNegativeInt(entry.recallCount);
|
||||
const dailyCount = toNonNegativeInt(entry.dailyCount);
|
||||
recallSignalCount += recallCount;
|
||||
dailySignalCount += dailyCount;
|
||||
totalSignalCount += recallCount + dailyCount;
|
||||
continue;
|
||||
}
|
||||
promotedTotal += 1;
|
||||
@@ -230,28 +277,74 @@ async function loadDreamingStoreStats(
|
||||
}
|
||||
}
|
||||
|
||||
let phaseSignalError: string | undefined;
|
||||
try {
|
||||
const phaseRaw = await fs.readFile(phaseSignalPath, "utf-8");
|
||||
const parsedPhase = JSON.parse(phaseRaw) as unknown;
|
||||
const phaseStore = asRecord(parsedPhase);
|
||||
const phaseEntries = asRecord(phaseStore?.entries) ?? {};
|
||||
for (const [key, value] of Object.entries(phaseEntries)) {
|
||||
if (!activeKeys.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const phaseEntry = asRecord(value);
|
||||
const lightHits = toNonNegativeInt(phaseEntry?.lightHits);
|
||||
const remHits = toNonNegativeInt(phaseEntry?.remHits);
|
||||
lightPhaseHitCount += lightHits;
|
||||
remPhaseHitCount += remHits;
|
||||
phaseSignalCount += lightHits + remHits;
|
||||
}
|
||||
} catch (err) {
|
||||
const code = (err as NodeJS.ErrnoException | undefined)?.code;
|
||||
if (code !== "ENOENT") {
|
||||
phaseSignalError = formatError(err);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
shortTermCount,
|
||||
recallSignalCount,
|
||||
dailySignalCount,
|
||||
totalSignalCount,
|
||||
phaseSignalCount,
|
||||
lightPhaseHitCount,
|
||||
remPhaseHitCount,
|
||||
promotedTotal,
|
||||
promotedToday,
|
||||
storePath,
|
||||
phaseSignalPath,
|
||||
...(latestPromotedAt ? { lastPromotedAt: latestPromotedAt } : {}),
|
||||
...(phaseSignalError ? { phaseSignalError } : {}),
|
||||
};
|
||||
} catch (err) {
|
||||
const code = (err as NodeJS.ErrnoException | undefined)?.code;
|
||||
if (code === "ENOENT") {
|
||||
return {
|
||||
shortTermCount: 0,
|
||||
recallSignalCount: 0,
|
||||
dailySignalCount: 0,
|
||||
totalSignalCount: 0,
|
||||
phaseSignalCount: 0,
|
||||
lightPhaseHitCount: 0,
|
||||
remPhaseHitCount: 0,
|
||||
promotedTotal: 0,
|
||||
promotedToday: 0,
|
||||
storePath,
|
||||
phaseSignalPath,
|
||||
};
|
||||
}
|
||||
return {
|
||||
shortTermCount: 0,
|
||||
recallSignalCount: 0,
|
||||
dailySignalCount: 0,
|
||||
totalSignalCount: 0,
|
||||
phaseSignalCount: 0,
|
||||
lightPhaseHitCount: 0,
|
||||
remPhaseHitCount: 0,
|
||||
promotedTotal: 0,
|
||||
promotedToday: 0,
|
||||
storePath,
|
||||
phaseSignalPath,
|
||||
storeError: formatError(err),
|
||||
};
|
||||
}
|
||||
@@ -259,23 +352,43 @@ async function loadDreamingStoreStats(
|
||||
|
||||
function mergeDreamingStoreStats(stats: DreamingStoreStats[]): DreamingStoreStats {
|
||||
let shortTermCount = 0;
|
||||
let recallSignalCount = 0;
|
||||
let dailySignalCount = 0;
|
||||
let totalSignalCount = 0;
|
||||
let phaseSignalCount = 0;
|
||||
let lightPhaseHitCount = 0;
|
||||
let remPhaseHitCount = 0;
|
||||
let promotedTotal = 0;
|
||||
let promotedToday = 0;
|
||||
let latestPromotedAtMs = Number.NEGATIVE_INFINITY;
|
||||
let lastPromotedAt: string | undefined;
|
||||
const storePaths = new Set<string>();
|
||||
const phaseSignalPaths = new Set<string>();
|
||||
const storeErrors: string[] = [];
|
||||
const phaseSignalErrors: string[] = [];
|
||||
|
||||
for (const stat of stats) {
|
||||
shortTermCount += stat.shortTermCount;
|
||||
recallSignalCount += stat.recallSignalCount;
|
||||
dailySignalCount += stat.dailySignalCount;
|
||||
totalSignalCount += stat.totalSignalCount;
|
||||
phaseSignalCount += stat.phaseSignalCount;
|
||||
lightPhaseHitCount += stat.lightPhaseHitCount;
|
||||
remPhaseHitCount += stat.remPhaseHitCount;
|
||||
promotedTotal += stat.promotedTotal;
|
||||
promotedToday += stat.promotedToday;
|
||||
if (stat.storePath) {
|
||||
storePaths.add(stat.storePath);
|
||||
}
|
||||
if (stat.phaseSignalPath) {
|
||||
phaseSignalPaths.add(stat.phaseSignalPath);
|
||||
}
|
||||
if (stat.storeError) {
|
||||
storeErrors.push(stat.storeError);
|
||||
}
|
||||
if (stat.phaseSignalError) {
|
||||
phaseSignalErrors.push(stat.phaseSignalError);
|
||||
}
|
||||
const promotedAtMs = stat.lastPromotedAt ? Date.parse(stat.lastPromotedAt) : Number.NaN;
|
||||
if (Number.isFinite(promotedAtMs) && promotedAtMs > latestPromotedAtMs) {
|
||||
latestPromotedAtMs = promotedAtMs;
|
||||
@@ -285,15 +398,27 @@ function mergeDreamingStoreStats(stats: DreamingStoreStats[]): DreamingStoreStat
|
||||
|
||||
return {
|
||||
shortTermCount,
|
||||
recallSignalCount,
|
||||
dailySignalCount,
|
||||
totalSignalCount,
|
||||
phaseSignalCount,
|
||||
lightPhaseHitCount,
|
||||
remPhaseHitCount,
|
||||
promotedTotal,
|
||||
promotedToday,
|
||||
...(storePaths.size === 1 ? { storePath: [...storePaths][0] } : {}),
|
||||
...(phaseSignalPaths.size === 1 ? { phaseSignalPath: [...phaseSignalPaths][0] } : {}),
|
||||
...(lastPromotedAt ? { lastPromotedAt } : {}),
|
||||
...(storeErrors.length === 1
|
||||
? { storeError: storeErrors[0] }
|
||||
: storeErrors.length > 1
|
||||
? { storeError: `${storeErrors.length} dreaming stores had read errors.` }
|
||||
: {}),
|
||||
...(phaseSignalErrors.length === 1
|
||||
? { phaseSignalError: phaseSignalErrors[0] }
|
||||
: phaseSignalErrors.length > 1
|
||||
? { phaseSignalError: `${phaseSignalErrors.length} phase signal stores had read errors.` }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -472,6 +597,12 @@ export const doctorHandlers: GatewayRequestHandlers = {
|
||||
)
|
||||
: {
|
||||
shortTermCount: 0,
|
||||
recallSignalCount: 0,
|
||||
dailySignalCount: 0,
|
||||
totalSignalCount: 0,
|
||||
phaseSignalCount: 0,
|
||||
lightPhaseHitCount: 0,
|
||||
remPhaseHitCount: 0,
|
||||
promotedTotal: 0,
|
||||
promotedToday: 0,
|
||||
};
|
||||
|
||||
@@ -106,31 +106,33 @@
|
||||
animation: dreams-float-z 4s ease-out infinite;
|
||||
}
|
||||
|
||||
.dreams__z:nth-child(1) {
|
||||
/* Z's originate from the lobster's head and float up-right at an angle.
|
||||
Use nth-of-type because Z's are <span> while stars/moon/glow are <div>. */
|
||||
.dreams__z:nth-of-type(1) {
|
||||
font-size: 14px;
|
||||
right: calc(50% - 100px);
|
||||
top: calc(50% - 80px);
|
||||
left: calc(50% + 70px);
|
||||
top: calc(50% - 90px);
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.dreams__z:nth-child(2) {
|
||||
.dreams__z:nth-of-type(2) {
|
||||
font-size: 20px;
|
||||
right: calc(50% - 130px);
|
||||
top: calc(50% - 120px);
|
||||
left: calc(50% + 100px);
|
||||
top: calc(50% - 130px);
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
|
||||
.dreams__z:nth-child(3) {
|
||||
.dreams__z:nth-of-type(3) {
|
||||
font-size: 28px;
|
||||
right: calc(50% - 160px);
|
||||
top: calc(50% - 170px);
|
||||
left: calc(50% + 130px);
|
||||
top: calc(50% - 175px);
|
||||
animation-delay: 2.4s;
|
||||
}
|
||||
|
||||
@keyframes dreams-float-z {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(10px) rotate(-5deg);
|
||||
transform: translate(0, 0) rotate(-10deg);
|
||||
}
|
||||
15% {
|
||||
opacity: 0.7;
|
||||
@@ -140,7 +142,7 @@
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(-40px) rotate(10deg);
|
||||
transform: translate(20px, -40px) rotate(15deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +169,7 @@
|
||||
|
||||
.dreams__bubble {
|
||||
position: absolute;
|
||||
top: calc(50% - 200px);
|
||||
top: calc(50% - 260px);
|
||||
left: calc(50% - 200px);
|
||||
padding: 16px 20px;
|
||||
background: var(--accent-subtle);
|
||||
|
||||
@@ -2103,7 +2103,8 @@ export function renderApp(state: AppViewState) {
|
||||
m.renderDreaming({
|
||||
active: dreamingOn,
|
||||
shortTermCount: state.dreamingStatus?.shortTermCount ?? 0,
|
||||
longTermCount: state.dreamingStatus?.promotedTotal ?? 0,
|
||||
totalSignalCount: state.dreamingStatus?.totalSignalCount ?? 0,
|
||||
phaseSignalCount: state.dreamingStatus?.phaseSignalCount ?? 0,
|
||||
promotedCount: state.dreamingStatus?.promotedToday ?? 0,
|
||||
dreamingOf: null,
|
||||
nextCycle: dreamingNextCycle,
|
||||
|
||||
@@ -39,6 +39,12 @@ describe("dreaming controller", () => {
|
||||
storageMode: "inline",
|
||||
separateReports: false,
|
||||
shortTermCount: 8,
|
||||
recallSignalCount: 14,
|
||||
dailySignalCount: 6,
|
||||
totalSignalCount: 20,
|
||||
phaseSignalCount: 11,
|
||||
lightPhaseHitCount: 7,
|
||||
remPhaseHitCount: 4,
|
||||
promotedTotal: 21,
|
||||
promotedToday: 2,
|
||||
phases: {
|
||||
@@ -82,6 +88,8 @@ describe("dreaming controller", () => {
|
||||
expect.objectContaining({
|
||||
enabled: true,
|
||||
shortTermCount: 8,
|
||||
totalSignalCount: 20,
|
||||
phaseSignalCount: 11,
|
||||
promotedToday: 2,
|
||||
phases: expect.objectContaining({
|
||||
deep: expect.objectContaining({
|
||||
|
||||
@@ -38,10 +38,18 @@ export type DreamingStatus = {
|
||||
storageMode: "inline" | "separate" | "both";
|
||||
separateReports: boolean;
|
||||
shortTermCount: number;
|
||||
recallSignalCount: number;
|
||||
dailySignalCount: number;
|
||||
totalSignalCount: number;
|
||||
phaseSignalCount: number;
|
||||
lightPhaseHitCount: number;
|
||||
remPhaseHitCount: number;
|
||||
promotedTotal: number;
|
||||
promotedToday: number;
|
||||
storePath?: string;
|
||||
phaseSignalPath?: string;
|
||||
storeError?: string;
|
||||
phaseSignalError?: string;
|
||||
phases: {
|
||||
light: LightDreamingStatus;
|
||||
deep: DeepDreamingStatus;
|
||||
@@ -142,7 +150,9 @@ function normalizeDreamingStatus(raw: unknown): DreamingStatus | null {
|
||||
const remRecord = asRecord(phasesRecord?.rem);
|
||||
const timezone = normalizeTrimmedString(record.timezone);
|
||||
const storePath = normalizeTrimmedString(record.storePath);
|
||||
const phaseSignalPath = normalizeTrimmedString(record.phaseSignalPath);
|
||||
const storeError = normalizeTrimmedString(record.storeError);
|
||||
const phaseSignalError = normalizeTrimmedString(record.phaseSignalError);
|
||||
|
||||
return {
|
||||
enabled: normalizeBoolean(record.enabled, false),
|
||||
@@ -151,10 +161,18 @@ function normalizeDreamingStatus(raw: unknown): DreamingStatus | null {
|
||||
storageMode: normalizeStorageMode(record.storageMode),
|
||||
separateReports: normalizeBoolean(record.separateReports, false),
|
||||
shortTermCount: normalizeFiniteInt(record.shortTermCount, 0),
|
||||
recallSignalCount: normalizeFiniteInt(record.recallSignalCount, 0),
|
||||
dailySignalCount: normalizeFiniteInt(record.dailySignalCount, 0),
|
||||
totalSignalCount: normalizeFiniteInt(record.totalSignalCount, 0),
|
||||
phaseSignalCount: normalizeFiniteInt(record.phaseSignalCount, 0),
|
||||
lightPhaseHitCount: normalizeFiniteInt(record.lightPhaseHitCount, 0),
|
||||
remPhaseHitCount: normalizeFiniteInt(record.remPhaseHitCount, 0),
|
||||
promotedTotal: normalizeFiniteInt(record.promotedTotal, 0),
|
||||
promotedToday: normalizeFiniteInt(record.promotedToday, 0),
|
||||
...(storePath ? { storePath } : {}),
|
||||
...(phaseSignalPath ? { phaseSignalPath } : {}),
|
||||
...(storeError ? { storeError } : {}),
|
||||
...(phaseSignalError ? { phaseSignalError } : {}),
|
||||
phases: {
|
||||
light: {
|
||||
...normalizePhaseStatusBase(lightRecord),
|
||||
|
||||
@@ -8,7 +8,8 @@ function buildProps(overrides?: Partial<DreamingProps>): DreamingProps {
|
||||
return {
|
||||
active: true,
|
||||
shortTermCount: 47,
|
||||
longTermCount: 182,
|
||||
totalSignalCount: 182,
|
||||
phaseSignalCount: 29,
|
||||
promotedCount: 12,
|
||||
dreamingOf: null,
|
||||
nextCycle: "4:00 AM",
|
||||
@@ -64,7 +65,7 @@ describe("dreaming view", () => {
|
||||
expect(values.length).toBe(3);
|
||||
expect(values[0]?.textContent).toBe("47");
|
||||
expect(values[1]?.textContent).toBe("182");
|
||||
expect(values[2]?.textContent).toBe("12");
|
||||
expect(values[2]?.textContent).toBe("29");
|
||||
});
|
||||
|
||||
it("shows dream bubble when active", () => {
|
||||
|
||||
@@ -55,7 +55,8 @@ function parseDiaryEntries(raw: string): DiaryEntry[] {
|
||||
export type DreamingProps = {
|
||||
active: boolean;
|
||||
shortTermCount: number;
|
||||
longTermCount: number;
|
||||
totalSignalCount: number;
|
||||
phaseSignalCount: number;
|
||||
promotedCount: number;
|
||||
dreamingOf: string | null;
|
||||
nextCycle: string | null;
|
||||
@@ -245,11 +246,11 @@ function renderScene(props: DreamingProps, idle: boolean, dreamText: string) {
|
||||
</div>
|
||||
<div
|
||||
class="dreams__bubble-dot"
|
||||
style="top: calc(50% - 100px); left: calc(50% - 80px); width: 12px; height: 12px; animation-delay: 0.2s;"
|
||||
style="top: calc(50% - 160px); left: calc(50% - 120px); width: 12px; height: 12px; animation-delay: 0.2s;"
|
||||
></div>
|
||||
<div
|
||||
class="dreams__bubble-dot"
|
||||
style="top: calc(50% - 70px); left: calc(50% - 50px); width: 8px; height: 8px; animation-delay: 0.4s;"
|
||||
style="top: calc(50% - 120px); left: calc(50% - 90px); width: 8px; height: 8px; animation-delay: 0.4s;"
|
||||
></div>
|
||||
`
|
||||
: nothing}
|
||||
@@ -284,16 +285,16 @@ function renderScene(props: DreamingProps, idle: boolean, dreamText: string) {
|
||||
<div class="dreams__stat-divider"></div>
|
||||
<div class="dreams__stat">
|
||||
<span class="dreams__stat-value" style="color: var(--accent);"
|
||||
>${props.longTermCount}</span
|
||||
>${props.totalSignalCount}</span
|
||||
>
|
||||
<span class="dreams__stat-label">Long-term</span>
|
||||
<span class="dreams__stat-label">Signals</span>
|
||||
</div>
|
||||
<div class="dreams__stat-divider"></div>
|
||||
<div class="dreams__stat">
|
||||
<span class="dreams__stat-value" style="color: var(--accent-2);"
|
||||
>${props.promotedCount}</span
|
||||
>${props.phaseSignalCount}</span
|
||||
>
|
||||
<span class="dreams__stat-label">Promoted Today</span>
|
||||
<span class="dreams__stat-label">Phase Hits</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user