Conversation
|
No actionable comments were generated in the recent review. 🎉 WalkthroughAdds a centralized frontend logger and integrates Tauri's native logging plugin; replaces scattered console.* calls across UI, hooks, and slices with the new logger; exposes logger API; updates Tauri permissions and initializes the tauri-plugin-log in the Rust main. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant App as Frontend App
participant Logger as utils/logger
participant Tauri as Tauri Runtime
participant Console as Browser Console
App->>Logger: logger.error("message", obj)
alt isTauriEnvironment() == true
Logger->>Tauri: emit log via tauri-plugin-log (formatted payload)
Tauri->>Tauri: write to native/OS log
else
Logger->>Console: console.error(formatted payload)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/features/imageSlice.ts (1)
29-33:⚠️ Potential issue | 🟡 MinorAsync side effect in Redux reducer (logger calls unhandled Promise from Tauri)
@tauri-apps/plugin-log'swarn()function returnsPromise<void>. The logger utility (@/utils/logger.tsline 55) callstauriWarn()withoutawaitor error handling, creating an unhandled Promise wheneverlogger.warn()is invoked in a Tauri environment. Redux reducers must be pure and synchronous; calling code that initiates async operations violates this constraint.The floating Promise risk is low here (error path, rarely hit), but should still be addressed. The suggested fix of adding
voidbeforelogger.warn()is ineffective sincelogger.warn()already returnsvoid. Instead:
Preferred: Add error handling in the logger utility itself:
// In logger.ts line 55 tauriWarn(stringify(...args)).catch((err) => { console.error('Logger error:', err); });Alternative: Extract this warning to middleware or a separate action handler outside the reducer.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/features/imageSlice.ts` around lines 29 - 33, The reducer in imageSlice calls logger.warn which delegates to tauriWarn (an async function) and can create an unhandled Promise; to fix this, modify the logger implementation (the tauriWarn invocation used by logger.warn) to attach a .catch handler that logs to console or swallow the error so the call remains synchronous from the reducer's perspective, e.g., ensure tauriWarn(...).catch(err => console.error('Logger error:', err)); do not introduce awaits or async calls inside the reducer—alternatively move the warning out of the reducer into middleware or an action handler if you want async handling centralized.
🧹 Nitpick comments (8)
frontend/src/utils/logger.ts (3)
28-73: CacheisTauriEnvironment()at module level — it's stable for the lifetime of the app
isTauriEnvironment()reads threewindowproperties on every single log call. The result never changes at runtime, so evaluating it repeatedly is unnecessary overhead.♻️ Proposed refactor
+const IS_TAURI = isTauriEnvironment(); + const logger = { trace(...args: unknown[]) { - if (isTauriEnvironment()) { + if (IS_TAURI) { tauriTrace(stringify(...args)); } else { console.debug('[TRACE]', ...args); } }, debug(...args: unknown[]) { - if (isTauriEnvironment()) { + if (IS_TAURI) { tauriDebug(stringify(...args)); } else { console.debug(...args); } }, // ... repeat for info, warn, error🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/logger.ts` around lines 28 - 73, isTauriEnvironment() is being called on every logger method call (trace, debug, info, warn, error, log) which is unnecessary because its result is constant for the app lifetime; cache its boolean result at module init (e.g., const IS_TAURI = isTauriEnvironment()) and update each method in the logger object to use that cached IS_TAURI instead of calling isTauriEnvironment() repeatedly, leaving stringify and tauriTrace/tauriDebug/tauriInfo/tauriWarn/tauriError intact and keeping logger.log as an alias to logger.info.
70-72:this.infobinding breaks iflogis destructuredconst { log } = logger; log('msg'); // TypeError: this is undefined (strict mode)Use an explicit call or an arrow function to avoid the implicit
thisdependency.♻️ Proposed fix
- log(...args: unknown[]) { - this.info(...args); - }, + log(...args: unknown[]) { + logger.info(...args); + },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/logger.ts` around lines 70 - 72, The log method relies on implicit this (log(...args) { this.info(...args) }) which breaks when destructured; change log to not depend on call-site this by either making it an arrow function (e.g., define log = (...args) => this.info(...args)) or explicitly forwarding to info using a bound call (e.g., call/apply this.info with the instance), updating the Logger class/instance where the methods are defined so destructured const { log } = logger; log('msg') still works.
1-75: No tests for the new centralized logger utility
logger.tsis now a critical cross-cutting utility used by a large number of components and hooks throughout the codebase. It contains conditional branching (Tauri vs. browser), custom serialization logic, and an environment detection dependency — all of which are testable in isolation. There are currently no unit tests covering any of these paths.Consider adding tests for at least:
stringify()with primitives,Errorobjects, circular references, andnull- Logger routing to
console.*in the non-Tauri branch (mockable viajest.spyOn)- Logger routing to Tauri functions in the Tauri branch (mock
isTauriEnvironment())🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/logger.ts` around lines 1 - 75, Add unit tests that exercise stringify and the logger branches: test stringify(...) with primitives, null, an Error instance (ensuring message and stack are included), and a circular reference (assert it doesn't throw and returns a string); test non-Tauri routing by mocking isTauriEnvironment to return false and using jest.spyOn on console.debug/info/warn/error to verify logger.debug/info/warn/error/log call the corresponding console methods with expected args; test Tauri routing by mocking isTauriEnvironment to return true and replacing tauriDebug/tauriInfo/tauriWarn/tauriError/tauriTrace with jest.fn() to assert they are called with the stringify(...) output when invoking logger.trace/debug/info/warn/error; import the exported stringify and default logger to locate and run these tests.frontend/src-tauri/Cargo.toml (1)
40-40: Loose version specifier inconsistent with other plugin dependenciesEvery other plugin in this file is pinned to a full
major.minor.patchversion (e.g.,tauri-plugin-shell = "2.3.3"). Using"2"fortauri-plugin-logaccepts any2.x.yrelease, making builds less reproducible and potentially pulling in breaking minor-version changes on a cleancargo update.-tauri-plugin-log = "2" +tauri-plugin-log = "2.2.3" # pin to the resolved version from Cargo.lock🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src-tauri/Cargo.toml` at line 40, The dependency for tauri-plugin-log uses a loose specifier ("2") which is inconsistent with other plugin entries; update tauri-plugin-log to a full semantic version pin (major.minor.patch) to match the format used by other plugins (for example, use the same full version as tauri-plugin-shell) so builds remain reproducible and avoid unintentional upgrades.frontend/src/hooks/selectFile.ts (2)
3-4: Missing blank line between the last import and theinterfacedeclaration✏️ Proposed fix
import logger from '@/utils/logger'; + interface UseFolderPickerOptions {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/hooks/selectFile.ts` around lines 3 - 4, Add a blank line after the last import to separate imports from type declarations: insert a single empty line between "import logger from '@/utils/logger';" and the "interface UseFolderPickerOptions" declaration to follow code-style conventions and improve readability.
34-34: Capitalization inconsistency in the log message
"Error picking File:"capitalizesFileunlike every other log message in this PR ("Error fetching videos:","Error reading or parsing folder paths from localStorage:", etc.).✏️ Proposed fix
- logger.error('Error picking File:', error); + logger.error('Error picking file:', error);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/hooks/selectFile.ts` at line 34, The log message in selectFile uses inconsistent capitalization ("Error picking File:"); update the logger.error call inside the selectFile hook (the logger.error(...) invocation) to use consistent lowercase for "file" (e.g., "Error picking file:") so it matches other messages like "Error fetching videos:" and maintain existing error argument passing.frontend/src/hooks/useUpdater.ts (1)
48-50:update.body(release notes) can produce large/multiline log entries.Consider logging
update.versionandupdate.dateseparately and omitting or truncatingupdate.body, which is often multi-paragraph Markdown.♻️ Suggested refactor
- logger.info( - `Found update ${update.version} from ${update.date} with notes ${update.body}`, - ); + logger.info(`Found update ${update.version} from ${update.date}`); + logger.debug(`Update notes: ${update.body}`);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/hooks/useUpdater.ts` around lines 48 - 50, The current logger.info call in useUpdater.ts inserts the full update.body which can be large/multiline; change the logging in the useUpdater logic to log update.version and update.date as separate fields and either omit update.body entirely or replace it with a truncated/sanitized summary (e.g., first N chars or single-line excerpt) before passing to logger.info; update the call site that uses logger.info(...) so it references update.version and update.date explicitly and uses a shortSummary variable instead of update.body.frontend/src/hooks/useFolder.ts (1)
19-36: Destructure theerrorfield fromusePictoMutationand include it in error logs for better debugging context.
addFolderErroris only the boolean flag (isError). The actual error object is available fromusePictoMutationvia theUseMutationResultspread and should be passed tologger.error(), matching the pattern used at line 52 withpickSingleFolder.♻️ Suggested refactor
const { mutate: addFolderMutate, isSuccess: addFolderSuccess, isError: addFolderError, isPending: addFolderPending, + error: addFolderErrorObj, } = usePictoMutation({ mutationFn: async (folder_path: string) => addFolder({ folder_path }), autoInvalidateTags: ['folders'], }); useEffect(() => { if (addFolderPending) { logger.info('Adding folder...'); } else if (addFolderSuccess) { logger.info('Folder added successfully'); } else if (addFolderError) { - logger.error('Error adding folder'); + logger.error('Error adding folder', addFolderErrorObj); } }, [addFolderSuccess, addFolderError, addFolderPending, addFolderErrorObj]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/hooks/useFolder.ts` around lines 19 - 36, Destructure the error object from usePictoMutation alongside mutate/isSuccess/isError/isPending (e.g., const { mutate: addFolderMutate, isSuccess: addFolderSuccess, isError: addFolderError, isPending: addFolderPending, error: addFolderErrorObj } = usePictoMutation(...)) and pass that error into the logger.error call inside the effect (replace logger.error('Error adding folder') with logger.error('Error adding folder', addFolderErrorObj)) so the actual error details from usePictoMutation (used for addFolder) are logged; follow the same pattern used for pickSingleFolder.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src-tauri/src/main.rs`:
- Line 193: The tauri_plugin_log plugin is currently initialized with
tauri_plugin_log::Builder::new().build() which leaves the logger with default
(too-verbose) levels and default KeepOne rotation; update the Builder
configuration before build() to set a sensible minimum log level (e.g., Info or
Warn), set a maximum file size cap, and choose an explicit rotation strategy
(e.g., Rotate or KeepAll as appropriate) so production builds don’t write
trace/debug logs to disk or silently discard history; locate the plugin
initialization call (tauri_plugin_log::Builder::new().build()) and chain the
appropriate methods on Builder to set level, file_size, and rotation before
calling build().
In `@frontend/src/components/Media/ImageCard.tsx`:
- Around line 81-85: The logger.debug call is executed unconditionally in the
onClick handler and can emit "Toggling favourite for image: undefined"; move the
logging into the existing guard inside handleToggleFavourite so it only runs
when image?.id is truthy. Specifically, remove the logger.debug from the onClick
lambda and add logger.debug('Toggling favourite for image:', image.id)
immediately inside the if (image?.id) block in handleToggleFavourite (which then
calls toggleFavourite(image.id)); ensure the useCallback dependencies for
handleToggleFavourite still include image and toggleFavourite.
In `@frontend/src/components/Memories/MemoryViewer.tsx`:
- Line 62: The current stringify implementation in logger.ts doesn't serialize
Error objects (they become {}), so update the stringify function to detect when
an argument is an Error and return a JSON string containing its name, message,
and stack; for other objects use JSON.stringify(arg, null, 2) and for primitives
use String(arg). Ensure this change affects all logging methods that call
stringify (e.g., logger.error used in MemoryViewer.tsx) so disk logs capture
useful error details.
In `@frontend/src/utils/logger.ts`:
- Around line 20-26: The stringify function currently loses Error details and
throws on circular structures; update stringify to detect Error instances and
return a stable representation including name, message, and stack (e.g., { name,
message, stack, ...enumerableProps }), and implement a circular-safe serializer:
use a replacer that tracks seen objects in a WeakSet and replaces circular
references with "[Circular]" (or fall back to String(obj) if serialization still
fails); keep non-object values as String(arg). Make these changes inside the
existing stringify(...) function so calls like logger.error(..., someError)
produce a useful string and the serializer no longer throws on circular
references.
---
Outside diff comments:
In `@frontend/src/features/imageSlice.ts`:
- Around line 29-33: The reducer in imageSlice calls logger.warn which delegates
to tauriWarn (an async function) and can create an unhandled Promise; to fix
this, modify the logger implementation (the tauriWarn invocation used by
logger.warn) to attach a .catch handler that logs to console or swallow the
error so the call remains synchronous from the reducer's perspective, e.g.,
ensure tauriWarn(...).catch(err => console.error('Logger error:', err)); do not
introduce awaits or async calls inside the reducer—alternatively move the
warning out of the reducer into middleware or an action handler if you want
async handling centralized.
---
Nitpick comments:
In `@frontend/src-tauri/Cargo.toml`:
- Line 40: The dependency for tauri-plugin-log uses a loose specifier ("2")
which is inconsistent with other plugin entries; update tauri-plugin-log to a
full semantic version pin (major.minor.patch) to match the format used by other
plugins (for example, use the same full version as tauri-plugin-shell) so builds
remain reproducible and avoid unintentional upgrades.
In `@frontend/src/hooks/selectFile.ts`:
- Around line 3-4: Add a blank line after the last import to separate imports
from type declarations: insert a single empty line between "import logger from
'@/utils/logger';" and the "interface UseFolderPickerOptions" declaration to
follow code-style conventions and improve readability.
- Line 34: The log message in selectFile uses inconsistent capitalization
("Error picking File:"); update the logger.error call inside the selectFile hook
(the logger.error(...) invocation) to use consistent lowercase for "file" (e.g.,
"Error picking file:") so it matches other messages like "Error fetching
videos:" and maintain existing error argument passing.
In `@frontend/src/hooks/useFolder.ts`:
- Around line 19-36: Destructure the error object from usePictoMutation
alongside mutate/isSuccess/isError/isPending (e.g., const { mutate:
addFolderMutate, isSuccess: addFolderSuccess, isError: addFolderError,
isPending: addFolderPending, error: addFolderErrorObj } = usePictoMutation(...))
and pass that error into the logger.error call inside the effect (replace
logger.error('Error adding folder') with logger.error('Error adding folder',
addFolderErrorObj)) so the actual error details from usePictoMutation (used for
addFolder) are logged; follow the same pattern used for pickSingleFolder.
In `@frontend/src/hooks/useUpdater.ts`:
- Around line 48-50: The current logger.info call in useUpdater.ts inserts the
full update.body which can be large/multiline; change the logging in the
useUpdater logic to log update.version and update.date as separate fields and
either omit update.body entirely or replace it with a truncated/sanitized
summary (e.g., first N chars or single-line excerpt) before passing to
logger.info; update the call site that uses logger.info(...) so it references
update.version and update.date explicitly and uses a shortSummary variable
instead of update.body.
In `@frontend/src/utils/logger.ts`:
- Around line 28-73: isTauriEnvironment() is being called on every logger method
call (trace, debug, info, warn, error, log) which is unnecessary because its
result is constant for the app lifetime; cache its boolean result at module init
(e.g., const IS_TAURI = isTauriEnvironment()) and update each method in the
logger object to use that cached IS_TAURI instead of calling
isTauriEnvironment() repeatedly, leaving stringify and
tauriTrace/tauriDebug/tauriInfo/tauriWarn/tauriError intact and keeping
logger.log as an alias to logger.info.
- Around line 70-72: The log method relies on implicit this (log(...args) {
this.info(...args) }) which breaks when destructured; change log to not depend
on call-site this by either making it an arrow function (e.g., define log =
(...args) => this.info(...args)) or explicitly forwarding to info using a bound
call (e.g., call/apply this.info with the instance), updating the Logger
class/instance where the methods are defined so destructured const { log } =
logger; log('msg') still works.
- Around line 1-75: Add unit tests that exercise stringify and the logger
branches: test stringify(...) with primitives, null, an Error instance (ensuring
message and stack are included), and a circular reference (assert it doesn't
throw and returns a string); test non-Tauri routing by mocking
isTauriEnvironment to return false and using jest.spyOn on
console.debug/info/warn/error to verify logger.debug/info/warn/error/log call
the corresponding console methods with expected args; test Tauri routing by
mocking isTauriEnvironment to return true and replacing
tauriDebug/tauriInfo/tauriWarn/tauriError/tauriTrace with jest.fn() to assert
they are called with the stringify(...) output when invoking
logger.trace/debug/info/warn/error; import the exported stringify and default
logger to locate and run these tests.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
frontend/src/hooks/useFolderOperations.tsx (1)
79-89: Redundant dual-log for a single error event.Both
logger.error(line 80) andlogger.warn(line 83) fire unconditionally together whenevertaggingStatusQuery.isErroristrue, producing two log entries for one failure. Thewarn'serrorMessageis derived from the same error already captured by theerrorcall, so it adds little value while mixing severity semantics (error+warnfor the same condition). Consolidate into a singlelogger.errorcall:♻️ Proposed consolidation
useEffect(() => { if (taggingStatusQuery.isError) { - logger.error('Failed to fetch tagging status:', taggingStatusQuery.error); - - const errorMessage = taggingStatusQuery.errorMessage || 'Unknown error'; - logger.warn(`Tagging status query failed: ${errorMessage}`); + const errorMessage = taggingStatusQuery.errorMessage || 'Unknown error'; + logger.error(`Failed to fetch tagging status: ${errorMessage}`, taggingStatusQuery.error); } }, [ taggingStatusQuery.isError, taggingStatusQuery.error, taggingStatusQuery.errorMessage, ]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/hooks/useFolderOperations.tsx` around lines 79 - 89, The code logs the same failure twice when taggingStatusQuery.isError is true; consolidate the two logs into a single error log by removing the redundant logger.warn and updating the logger.error call (in useFolderOperations.tsx around the taggingStatusQuery handling) to include the derived errorMessage (taggingStatusQuery.errorMessage || 'Unknown error') alongside taggingStatusQuery.error so only one descriptive error-level entry is emitted for the taggingStatusQuery failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@frontend/src/hooks/useFolderOperations.tsx`:
- Around line 79-89: The code logs the same failure twice when
taggingStatusQuery.isError is true; consolidate the two logs into a single error
log by removing the redundant logger.warn and updating the logger.error call (in
useFolderOperations.tsx around the taggingStatusQuery handling) to include the
derived errorMessage (taggingStatusQuery.errorMessage || 'Unknown error')
alongside taggingStatusQuery.error so only one descriptive error-level entry is
emitted for the taggingStatusQuery failure.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
frontend/src/utils/logger.ts (2)
103-106:this.info(...)inlogbreaks if the method is destructured.const { log } = logger; log('hello'); // TypeError: Cannot read properties of undefined (reading 'info')Since
loggeris a module-levelconst, referencing it directly avoids thethisbinding issue entirely (the method body only runs afterloggeris fully initialised).♻️ Proposed fix
- /** Alias for info — drop-in replacement for console.log */ - log(...args: unknown[]) { - this.info(...args); - }, + /** Alias for info — drop-in replacement for console.log */ + log(...args: unknown[]) { + logger.info(...args); + },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/logger.ts` around lines 103 - 106, The log method currently uses this.info(...args) which fails when the method is destructured; change the implementation of the log method to call the module-level logger directly (use logger.info(...args)) so it doesn't rely on this binding—update the log(...) method in the logger object to reference the top-level logger const and call its info method.
62-101: CacheisTauriEnvironment()at module scope instead of calling it on every log invocation.
isTauriEnvironment()involves atry/catchplus two'in window'guard checks and is invoked once per log call across all five methods. Since the runtime environment is static for the app's lifetime, re-evaluating this on everylogger.*call is unnecessary overhead.♻️ Proposed refactor
+const IS_TAURI = isTauriEnvironment(); + const logger = { trace(...args: unknown[]) { - if (isTauriEnvironment()) { + if (IS_TAURI) { tauriTrace(stringify(...args)); } else { console.debug('[TRACE]', ...args); } }, debug(...args: unknown[]) { - if (isTauriEnvironment()) { + if (IS_TAURI) { tauriDebug(stringify(...args)); } else { console.debug(...args); } }, info(...args: unknown[]) { - if (isTauriEnvironment()) { + if (IS_TAURI) { tauriInfo(stringify(...args)); } else { console.info(...args); } }, warn(...args: unknown[]) { - if (isTauriEnvironment()) { + if (IS_TAURI) { tauriWarn(stringify(...args)); } else { console.warn(...args); } }, error(...args: unknown[]) { - if (isTauriEnvironment()) { + if (IS_TAURI) { tauriError(stringify(...args)); } else { console.error(...args); } },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/logger.ts` around lines 62 - 101, Cache the result of isTauriEnvironment() in a module-level constant (e.g., const IS_TAURI = isTauriEnvironment()) and use that constant inside logger.trace, logger.debug, logger.info, logger.warn, and logger.error instead of calling isTauriEnvironment() on every invocation; update each method to branch on the cached IS_TAURI value while leaving tauriTrace/tauriDebug/tauriInfo/tauriWarn/tauriError and stringify usage unchanged so the runtime environment is evaluated once at module load.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@frontend/src/utils/logger.ts`:
- Around line 103-106: The log method currently uses this.info(...args) which
fails when the method is destructured; change the implementation of the log
method to call the module-level logger directly (use logger.info(...args)) so it
doesn't rely on this binding—update the log(...) method in the logger object to
reference the top-level logger const and call its info method.
- Around line 62-101: Cache the result of isTauriEnvironment() in a module-level
constant (e.g., const IS_TAURI = isTauriEnvironment()) and use that constant
inside logger.trace, logger.debug, logger.info, logger.warn, and logger.error
instead of calling isTauriEnvironment() on every invocation; update each method
to branch on the cached IS_TAURI value while leaving
tauriTrace/tauriDebug/tauriInfo/tauriWarn/tauriError and stringify usage
unchanged so the runtime environment is evaluated once at module load.
Fixes: #1091
@rahulharpal1603
Issue - The core issue was dumping the full image object (file paths, metadata, favourite status) into the browser devtools in production.
Steps to reproduce:
1 Open PictoPy and navigate to Home page
2 Open browser developer console (F12)
3 Hover over any image and click the heart icon (favorite)
4 Result: Image data is logged to console (privacy concern)
Solution Implemented:
To view the logs live in the terminal use the below command:
Get-Content "C:\Users\<username>\AppData\Local\org.aossie.pictopy\logs\PictoPy.log" -Wait -Tail 50@aurthitaacharya-afk I also tried contacting you, look into this pr and if you have a better approach do let me know!
Summary by CodeRabbit
New Features
Chores