fix: slow get recipe memo

- fix: cannot adb command from blocking suspicious cmd
- change: logging to logtape lib
- change: adb payload handler format to send payload size as header for exact match payload

Signed-off-by: pakintada@gmail.com <Pakin>
This commit is contained in:
pakintada@gmail.com 2026-07-01 17:08:46 +07:00
parent aa6414b1cc
commit 3cda8aa60b
12 changed files with 494 additions and 174 deletions

BIN
bun.lockb

Binary file not shown.

View file

@ -65,6 +65,7 @@
"@dnd-kit-svelte/svelte": "0.1.3", "@dnd-kit-svelte/svelte": "0.1.3",
"@dnd-kit/abstract": "^0.2.4", "@dnd-kit/abstract": "^0.2.4",
"@dnd-kit/helpers": "^0.2.4", "@dnd-kit/helpers": "^0.2.4",
"@logtape/logtape": "^2.2.2",
"@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/match-sorter-utils": "^8.19.4",
"@types/semver": "^7.7.1", "@types/semver": "^7.7.1",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
@ -77,6 +78,7 @@
"animejs": "^4.3.6", "animejs": "^4.3.6",
"firebase": "^12.14.0", "firebase": "^12.14.0",
"idb": "^8.0.3", "idb": "^8.0.3",
"logtape": "https://github.com/dahlia/logtape.git",
"mode-watcher": "^1.1.0", "mode-watcher": "^1.1.0",
"semver": "^7.8.4", "semver": "^7.8.4",
"usb": "^2.17.0", "usb": "^2.17.0",

View file

@ -1,4 +1,5 @@
<script lang="ts">import { logger } from '$lib/core/utils/logger'; <script lang="ts">
import { logger } from '$lib/core/utils/logger';
import { Button, type ButtonVariant } from './ui/button'; import { Button, type ButtonVariant } from './ui/button';
import * as Card from './ui/card/index'; import * as Card from './ui/card/index';
@ -104,7 +105,9 @@
if (instance) { if (instance) {
try { try {
// bypass // bypass
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass'); await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass', {
bypass: true
});
} catch (e) {} } catch (e) {}
let result = await adb.executeCmd( let result = await adb.executeCmd(
@ -129,7 +132,9 @@
try { try {
// bypass // bypass
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass'); await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass', {
bypass: true
});
} catch (e) {} } catch (e) {}
setTimeout(async () => { setTimeout(async () => {

View file

@ -18,10 +18,14 @@ import { WritableStream } from '@yume-chan/stream-extra';
import { env } from '$env/dynamic/public'; import { env } from '$env/dynamic/public';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { adbConnectionStatus } from '../stores/adbConnectionStore'; import { adbConnectionStatus } from '../stores/adbConnectionStore';
import { browser } from '$app/environment';
import AdbWorker from '$lib/workers/androidPayload.worker?worker';
let _connectionPromise: Promise<boolean> | null = null; let _connectionPromise: Promise<boolean> | null = null;
let syncConnection: any = null; let syncConnection: any = null;
let worker: Worker | undefined;
/** /**
* Centralized connection function that all pages and components should use. * Centralized connection function that all pages and components should use.
* *
@ -392,8 +396,8 @@ function sanitizeAdbCommand(command: string): string {
return command; return command;
} }
export async function executeCmd(command: string) { export async function executeCmd(command: string, { bypass = false }) {
command = sanitizeAdbCommand(command); command = !bypass ? sanitizeAdbCommand(command) : command;
let instance = getAdbInstance(); let instance = getAdbInstance();
if (!instance) { if (!instance) {
@ -444,7 +448,7 @@ export async function executeCmd(command: string) {
export async function goToMachineHome() { export async function goToMachineHome() {
if (!getAdbInstance()) return; if (!getAdbInstance()) return;
try { try {
await executeCmd('input keyevent KEYCODE_HOME'); await executeCmd('input keyevent KEYCODE_HOME', { bypass: true });
} catch (e) { } catch (e) {
console.error('[goToMachineHome] error', e); console.error('[goToMachineHome] error', e);
} }
@ -687,52 +691,80 @@ async function connectToAndroidServer(maxRetries = 5) {
const writer = stream.writable.getWriter(); const writer = stream.writable.getWriter();
const reader = stream.readable.getReader(); const reader = stream.readable.getReader();
if (browser) {
worker = new AdbWorker();
worker.onmessage = (e) => {
if (e.data.type === 'DATA') {
const raw = e.data.payload;
handleAdbPayload(new TextDecoder().decode(raw));
}
};
// logger.info("prep post ");
// worker.postMessage({ stream }, [stream]);
}
// const reader = stream.readable.getReader();
// logger.info('checking on writer ', writer); // logger.info('checking on writer ', writer);
adbWriter.set(writer); adbWriter.set(writer);
if (writer) { if (writer) {
addNotification('INFO:Enable Brewing Mode T on machine'); addNotification('INFO:Enable Brewing Mode T on machine');
const textDecoder = new TextDecoder();
let buffer = '';
(async () => { (async () => {
try { while (true) {
while (true) { const { value, done } = await reader.read();
const { value, done } = await reader.read(); if (done) break;
if (done) break;
// decode chunk // Transfer ONLY the buffer, not the stream
buffer += textDecoder.decode(value, { stream: true }); // Type assertion (as any) fixes the overload error
(worker as any).postMessage({ type: 'CHUNK', payload: value }, [value.buffer]);
}
})();
let lines = buffer.split('\n'); // const textDecoder = new TextDecoder();
// let buffer = '';
// save potential incomplete // (async () => {
buffer = lines.pop() ?? ''; // try {
// while (true) {
// const { value, done } = await reader.read();
// if (done) break;
for (const line of lines) { // // decode chunk
if (line.trim() === '') continue; // buffer += textDecoder.decode(value, { stream: true });
GlobalEventBus.emit('adb:raw-payload', line); // let lines = buffer.split('\n');
handleAdbPayload(line);
}
// GlobalEventBus.emit('adb:raw-payload', new TextDecoder().decode(value)); // // save potential incomplete
// handleAdbPayload(new TextDecoder().decode(value)); // buffer = lines.pop() ?? '';
}
} catch (e) { // for (const line of lines) {
logger.error('read error', e); // if (line.trim() === '') continue;
if (isRecoverableError(e)) {
void connectToAndroidServer(); // GlobalEventBus.emit('adb:raw-payload', line);
} // handleAdbPayload(line);
} finally { // }
adbWriter.set(null);
addNotification('WARN:Brewing Mode T Offline ...'); // // GlobalEventBus.emit('adb:raw-payload', new TextDecoder().decode(value));
reader.cancel().catch(() => {}); // // handleAdbPayload(new TextDecoder().decode(value));
} // }
})(); // } catch (e) {
return; // logger.error('read error', e);
} else { // if (isRecoverableError(e)) {
addNotification('WARN:Brewing Mode T unavailable'); // void connectToAndroidServer();
// }
// } finally {
// adbWriter.set(null);
// addNotification('WARN:Brewing Mode T Offline ...');
// reader.cancel().catch(() => {});
// }
// })();
return;
} else {
addNotification('WARN:Brewing Mode T unavailable');
if (attempt < maxRetries - 1) { if (attempt < maxRetries - 1) {
const delay = Math.min(500 * Math.pow(2, attempt) + Math.random() * 500, 5000); const delay = Math.min(500 * Math.pow(2, attempt) + Math.random() * 500, 5000);

View file

@ -347,8 +347,73 @@ async function handleAdbPayload(raw_payload: string) {
recipeFromMachine.set(recipeRawFromMachine); recipeFromMachine.set(recipeRawFromMachine);
} else { } else {
if (sub_type == null) {
try {
let big_recipe = JSON.parse(JSON.stringify(payload.payload));
logger.info(`get large recipe keys: ${Object.keys(big_recipe)}`);
let recipes = JSON.parse(big_recipe?.recipes);
let materials = JSON.parse(big_recipe?.materials);
let toppings = JSON.parse(big_recipe?.toppings);
let updated = {
Recipe01: recipes,
MaterialSetting: materials,
Topping: toppings
};
recipeFromMachine.set(updated);
// =====================================
//
// Build Overview
//
let queries = [];
for (let rp of recipes) {
queries.push({
productCode: rp.productCode ?? '<not set>',
name: rp.name ? rp.name : (rp.otherName ?? '<not set>'),
description: rp.description
? rp.description
: (rp.otherDescription ?? '<not set>'),
tags: buildTags(rp),
status: getMenuStatus(rp.MenuStatus)
});
}
let cached_query_from_machine = get(recipeFromMachineQuery);
cached_query_from_machine = {
...cached_query_from_machine,
recipe: queries
};
recipeFromMachineQuery.set(cached_query_from_machine);
// =====================================
//
// Send finish signal
GlobalEventBus.emitUntilConsumed('recipe-event', {
type: 'load-recipe',
status: 'end',
reload: true
});
} catch (e) {
logger.error('some promise failed: ', e);
GlobalEventBus.emitUntilConsumed('recipe-event', {
type: 'load-recipe-fail',
status: 'end',
reload: true
});
}
} else {
logger.info(`unhandled sub type: %${sub_type}%`);
}
// unhandled sub type // unhandled sub type
logger.info('unhandled sub type', payload);
} }
break; break;

View file

@ -529,7 +529,7 @@ const handlers: Record<string, (payload: any) => void> = {
heartbeat: (p) => { heartbeat: (p) => {
socketConnectionOfflineCount.set(0); socketConnectionOfflineCount.set(0);
socketAlreadySendHeartbeat.set(0); socketAlreadySendHeartbeat.set(0);
logger.info('heartbeat reset offline count'); // logger.info('heartbeat reset offline count');
}, },
// Raw stream handlers for sheet data (e.g., price) // Raw stream handlers for sheet data (e.g., price)
raw_stream: (p) => { raw_stream: (p) => {

View file

@ -1,91 +1,216 @@
/** /**
* Lightweight logger that suppresses output in production. * Structured logger powered by LogTape (@logtape/logtape).
* *
* In development (npm run dev): logs are shown with a prefix tag. * Two tiers of logging:
* In production (npm run build): all console output is suppressed * - **logger** general-purpose logging (suppressed in production for security)
* so logs are never exposed to the browser inspector. * - Dev: `trace`, `debug`, `info`, `warn`, `error` (configurable via PUBLIC_APP_LOG_LEVEL)
* - Prod: `info` by default (or what PUBLIC_APP_LOG_LEVEL says)
* - **inspector** error-only logging, ALWAYS active (never suppressed).
* Use for security-critical events that must appear in the browser
* developer console (e.g. auth failures, API errors).
* *
* Usage: * API (100 % backward compatible):
* import { logger } from '$lib/core/utils/logger'; * logger.info('message', arg1, )
* logger.info('hello', someVar); * logger.error('Failed:', err)
* logger.error('failed', err); * inspector.error('auth failure', uid) // always visible
*/ */
import { configureSync, getConsoleSink, withFilter, getLogger } from '@logtape/logtape';
type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error'; // ---------------------------------------------------------------------------
// Environment helpers
// ---------------------------------------------------------------------------
const LOG_LEVEL_RANK: Record<LogLevel, number> = { const IS_DEV = import.meta.env.DEV;
trace: -1, const ENV_LOG_LEVEL = (import.meta.env.PUBLIC_APP_LOG_LEVEL as string) || (IS_DEV ? 'trace' : 'info');
debug: 0,
info: 1, // ---------------------------------------------------------------------------
warn: 2, // LogTape configuration (singleton guard for Vite HMR / config reload)
error: 3 // ---------------------------------------------------------------------------
let _configured = false;
function ensureConfigured() {
if (_configured) return;
_configured = true;
configureSync({
sinks: {
console: getConsoleSink(),
errorsOnly: withFilter(getConsoleSink(), 'error'),
},
loggers: [
{
category: ['supra-app'],
sinks: ['console'],
lowestLevel: ENV_LOG_LEVEL as 'trace' | 'debug' | 'info' | 'warning' | 'error' | 'fatal',
},
{
category: ['supra-app', 'inspector'],
sinks: ['errorsOnly'],
lowestLevel: 'error',
},
{
category: ['logtape', 'meta'],
sinks: ['console'],
lowestLevel: 'error',
},
],
});
}
// ---------------------------------------------------------------------------
// Internal LogTape loggers (lazy-init)
// ---------------------------------------------------------------------------
function getBaseLogger() {
ensureConfigured();
return getLogger(['supra-app']);
}
function getInspectorLogger() {
ensureConfigured();
return getLogger(['supra-app', 'inspector']);
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
function isRecord(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !(v instanceof Error) && !Array.isArray(v);
}
/**
* Convert variadic args into a flat message string + optional extras.
*
* Patterns handled:
* logger.info('text') message, no props
* logger.info('text', obj) message, obj as props if obj is a plain Record
* logger.info('text', error) error overload
* logger.info('text', arg1, arg2, ) concatenated message
*/
function normalize(
args: unknown[],
): { message: string; error?: Error; properties?: Record<string, unknown> } {
if (args.length === 0) return { message: '' };
const first = args[0];
if (args.length === 1) {
if (first instanceof Error) return { message: first.message, error: first };
return { message: String(first) };
}
if (args.length === 2 && isRecord(args[1])) {
return { message: String(first), properties: args[1] };
}
// error overload: logger.warn(msg, error) / logger.error('msg', err)
if (args.length === 2 && args[1] instanceof Error) {
return { message: String(first), error: args[1] as Error };
}
// multi-arg: concatenate
return {
message: args.map((a) => (typeof a === 'object' ? safeStringify(a) : String(a))).join(' '),
};
}
function safeStringify(v: unknown): string {
if (v instanceof Error) return `${v.name}: ${v.message}`;
try {
return JSON.stringify(v);
} catch {
return String(v);
}
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
export const logger = {
trace(...args: unknown[]) {
const { message } = normalize(args);
getBaseLogger().trace(message);
},
debug(...args: unknown[]) {
const { message, properties } = normalize(args);
if (properties) {
getBaseLogger().debug(message, properties);
} else {
getBaseLogger().debug(message);
}
},
info(...args: unknown[]) {
const { message, properties } = normalize(args);
if (properties) {
getBaseLogger().info(message, properties);
} else {
getBaseLogger().info(message);
}
},
warn(...args: unknown[]) {
const { message, error, properties } = normalize(args);
if (properties) {
getBaseLogger().warn(message, properties);
} else if (error) {
getBaseLogger().warn(message, error);
} else {
getBaseLogger().warn(message);
}
},
error(...args: unknown[]) {
const { message, error, properties } = normalize(args);
if (error && properties) {
getBaseLogger().error(error, properties);
} else if (error) {
getBaseLogger().error(error);
} else if (properties) {
getBaseLogger().error(message, properties);
} else {
getBaseLogger().error(message);
}
},
}; };
const ENV_LOG_LEVEL: LogLevel = // ---------------------------------------------------------------------------
(import.meta.env.PUBLIC_APP_LOG_LEVEL as LogLevel) || 'info'; // ---------------------------------------------------------------------------
const IS_DEV = import.meta.env.DEV; // ---------------------------------------------------------------------------
function shouldLog(level: LogLevel): boolean {
return IS_DEV && LOG_LEVEL_RANK[level] >= LOG_LEVEL_RANK[ENV_LOG_LEVEL];
}
function formatArgs(level: LogLevel, args: any[]): any[] {
return [`[${level.toUpperCase()}]`, ...args];
}
/** /**
* Extract caller location (file:line) from a stack trace. * Inspector logger always active, error-only, never suppressed.
* Stack frame format: " at method (file:///path/file.ts:10:20)" * Messages are written via the same LogTape infrastructure but on a
* The 3rd frame (index 2) is the caller of the method that created the Error. * separate category (supra-app inspector) whose sink is hard-filtered to
* `error` / `fatal` so security-sensitive events are always visible.
*/ */
function getCallerLocation(): string { export const inspector = {
try { error(...args: unknown[]) {
const stack = new Error().stack?.split('\n'); const { message, error, properties } = normalize(args);
if (!stack || stack.length < 3) return 'unknown'; if (error && properties) {
// stack[0] = "Error", stack[1] = " at getCallerLocation (...)", getInspectorLogger().error(error, properties);
// stack[2] = " at Logger.trace (...)" — skip both } else if (error) {
const caller = stack[2]?.trim(); getInspectorLogger().error(error);
// Extract path from common formats: } else if (properties) {
// "at method (file:///path/file.ts:10:20)" or "at file:///path/file.ts:10:20" getInspectorLogger().error(message, properties);
const match = caller.match(/(?:\(|at\s+)([^(]+?\.\w+):(\d+)/); } else {
if (match) return `${match[1]}:${match[2]}`; getInspectorLogger().error(message);
return caller || 'unknown';
} catch {
return 'unknown';
}
}
class Logger {
trace(...args: any[]) {
if (shouldLog('trace')) {
const location = getCallerLocation();
console.debug(`[TRACE:${location}]`, ...args);
} }
} },
debug(...args: any[]) { warn(...args: unknown[]) {
if (shouldLog('debug')) { const { message, error } = normalize(args);
console.debug(...formatArgs('debug', args)); // inspector only logs errors; escalate to error level
if (error) {
getInspectorLogger().error(error);
} else if (message) {
getInspectorLogger().error(message);
} }
} },
info(...args: any[]) { info(...args: unknown[]) {
if (shouldLog('info')) { const { message } = normalize(args);
console.info(...formatArgs('info', args)); // promote to error so it passes the filter
} if (message) getInspectorLogger().error(message);
} },
};
warn(...args: any[]) {
if (shouldLog('warn')) {
console.warn(...formatArgs('warn', args));
}
}
error(...args: any[]) {
if (shouldLog('error')) {
console.error(...formatArgs('error', args));
}
}
}
export const logger = new Logger();

View file

@ -0,0 +1,70 @@
/**
* Custom Vite logger that routes Vite's build/dev-server messages
* through our LogTape-powered logger for consistent formatting.
*
* Usage in vite.config.ts:
* import { createViteLogger } from './src/lib/core/utils/vite-logger';
* export default defineConfig({
* customLogger: createViteLogger(),
* // ...
* });
*/
import type { LogOptions, LogErrorOptions } from 'vite';
import type { Logger } from 'vite';
import { logger } from './logger';
export function createViteLogger(): Logger {
const warnedMessages = new Set<string>();
return {
get hasWarned() {
return warnedMessages.size > 0;
},
info(msg: string, options?: LogOptions) {
const prefix = options?.environment ? `[${options.environment}]` : '';
if (prefix) {
logger.info(`${prefix} ${msg}`);
} else if (options?.timestamp) {
logger.info(`[vite] ${msg}`);
} else {
logger.info(msg);
}
},
warn(msg: string, options?: LogOptions) {
warnedMessages.add(msg);
const prefix = options?.environment ? `[${options.environment}]` : '';
if (prefix) {
logger.warn(`${prefix} ${msg}`);
} else {
logger.warn(msg);
}
},
warnOnce(msg: string, options?: LogOptions) {
if (warnedMessages.has(msg)) return;
this.warn(msg, options);
},
error(msg: string, options?: LogErrorOptions) {
const prefix = options?.environment ? `[${options.environment}]` : '';
const err = options?.error;
const fullMsg = prefix ? `${prefix} ${msg}` : msg;
if (err && err instanceof Error) {
logger.error(fullMsg, err);
} else {
logger.error(fullMsg);
}
},
clearScreen() {
// Let Vite handle screen clearing itself
},
hasErrorLogged(_error: Error | import('rollup').RollupError): boolean {
// Don't keep error state — always re-report
return false;
},
};
}

View file

@ -0,0 +1,63 @@
/// <reference lib="webworker" />
import { logger } from '$lib/core/utils/logger';
let chunks: Uint8Array[] = [];
let totalBytes = 0;
let expectedLength = -1;
self.onmessage = async (e) => {
if (e.data.type === 'CHUNK') {
const chunk = e.data.payload;
chunks.push(chunk);
totalBytes += chunk.length;
// Loop to process complete messages
while (true) {
// 1. Try to get length header (need 4 bytes)
if (expectedLength === -1 && totalBytes >= 4) {
// Need a flat buffer just to read the header
const headerBuf = concatChunks(chunks, 4);
expectedLength = new DataView(headerBuf.buffer).getInt32(0, false);
// Remove header bytes from our chunks array
removeBytes(4);
}
// 2. Try to get payload
if (expectedLength !== -1 && totalBytes >= expectedLength) {
const payload = concatChunks(chunks, expectedLength);
removeBytes(expectedLength);
self.postMessage({ type: 'DATA', payload }, [payload.buffer]);
expectedLength = -1; // Reset for next message
} else {
break; // Wait for more data
}
}
}
};
// Helper: Concatenate only what we need
function concatChunks(chunks: Uint8Array[], length: number): Uint8Array {
const result = new Uint8Array(length);
let offset = 0;
for (let i = 0; i < chunks.length && offset < length; i++) {
const chunk = chunks[i];
const toCopy = Math.min(chunk.length, length - offset);
result.set(chunk.slice(0, toCopy), offset);
// If we only used part of the chunk, we keep the rest in the array
if (toCopy < chunk.length) {
chunks[i] = chunk.slice(toCopy);
} else {
chunks.shift();
i--;
}
offset += toCopy;
}
return result;
}
// Helper: Update total count
function removeBytes(n: number) {
totalBytes -= n;
}

View file

@ -121,6 +121,7 @@
try { try {
addNotification('WARN:Load recipe from app memories ...'); addNotification('WARN:Load recipe from app memories ...');
logger.info('sending request get recipe');
sendToAndroid({ sendToAndroid({
type: 'get_recipe', type: 'get_recipe',
payload: {} payload: {}
@ -290,11 +291,14 @@
if (instance) { if (instance) {
try { try {
// bypass // bypass
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass'); await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass', {
bypass: true
});
} catch (e) {} } catch (e) {}
let result = await adb.executeCmd( let result = await adb.executeCmd(
'am start -n com.forthvending.coffeemain/com.forthvending.coffeemain.MainActivity' 'am start -n com.forthvending.coffeemain/com.forthvending.coffeemain.MainActivity',
{ bypass: true }
); );
// if (result?.output) { // if (result?.output) {
// toast.success('Open app success!'); // toast.success('Open app success!');
@ -315,13 +319,15 @@
try { try {
// bypass // bypass
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass'); await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass', {
bypass: true
});
} catch (e) {} } catch (e) {}
setTimeout(async () => { setTimeout(async () => {
try { try {
// bypass // bypass
await adb.executeCmd('input tap 336 795'); await adb.executeCmd('input tap 336 795', { bypass: true });
} catch (e) {} } catch (e) {}
}, 3000); }, 3000);
} }
@ -846,57 +852,6 @@
recipeAutoLoadAttempted = true; recipeAutoLoadAttempted = true;
void loadBrewDataFromConnectedAdb(); void loadBrewDataFromConnectedAdb();
}); });
$effect(() => {
const brewAppStatusInterval = setInterval(async () => {
// schedule status from .brew_web_status.log
let inst = adb.getAdbInstance();
if (inst && devRecipe && !recipeLoading) {
await adb.executeCmd(
'tail -n 1 /sdcard/coffeevending/.brew_web_status.log > /sdcard/coffeevending/.brew_web_status.latest.log'
);
const latestStatusPath = env.PUBLIC_BREW_WEB_LATEST_STATUS;
if (!latestStatusPath) return;
let brew_status_log = await adb.pull(latestStatusPath);
if (brew_status_log) {
let latest_log = brew_status_log;
if (brew_status !== latest_log) {
brew_status = latest_log;
// noti from machine
if (latest_log.includes('!!!')) {
let log = latest_log.split('!!!');
let noti_cfg = log[1];
if (noti_cfg.includes('=')) {
let noti_level = noti_cfg.split('=')[1];
let spl = log[0].split(':');
let pure_msg = spl[spl.length - 1];
// case special message
if (pure_msg.includes('starting retry process')) {
// is waiting/idle process
pure_msg = 'Wait for brewing';
}
addNotification(`${noti_level}:${pure_msg}`);
}
}
}
}
} else if (inst && !devRecipe) {
// try again
// await startFetchRecipeFromMachine();
}
}, 1000);
return () => {
clearInterval(brewAppStatusInterval);
};
});
</script> </script>
<div class="mx-8 flex"> <div class="mx-8 flex">

View file

@ -10,7 +10,8 @@
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"moduleResolution": "bundler" "moduleResolution": "bundler",
"lib": ["ESNext", "DOM", "WebWorker"]
} }
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files

View file

@ -2,8 +2,10 @@ import devtoolsJson from 'vite-plugin-devtools-json';
import tailwindcss from '@tailwindcss/vite'; import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vitest/config'; import { defineConfig } from 'vitest/config';
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import { createViteLogger } from './src/lib/core/utils/vite-logger';
export default defineConfig({ export default defineConfig({
customLogger: createViteLogger(),
plugins: [tailwindcss(), sveltekit(), devtoolsJson()], plugins: [tailwindcss(), sveltekit(), devtoolsJson()],
ssr: { ssr: {
noExternal: ['@dnd-kit/core', '@dnd-kit/sortable'] noExternal: ['@dnd-kit/core', '@dnd-kit/sortable']