diff --git a/.github/CLAUDE.md b/.github/CLAUDE.md index 7fab5908..394da5bd 100644 --- a/.github/CLAUDE.md +++ b/.github/CLAUDE.md @@ -334,4 +334,100 @@ To update this file, ensure that all sections are kept current with the latest a - Add new sections as needed for significant changes in the project structure or processes. - Ensure all technical terms are explained or linked to relevant documentation. - Periodically review for outdated information and remove or update as necessary. -- If told to not do something, ensure this is also added to the "Dont Do that" section. \ No newline at end of file +- If told to not do something, ensure this is also added to the "Dont Do that" section. + +# Karpathy Guidelines + +Behavioral guidelines to reduce common LLM coding mistakes. + +**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. + +## 1. Clarify Before Executing + +**Resolve ambiguity up front — before spawning agents or starting any autonomous work.** + +Do this in order, then stop and ask before proceeding: + +1. **Understand the request.** Read what was asked closely and state your assumptions explicitly. +2. **Inspect the codebase.** Check the relevant code, conventions, and existing patterns. Note anything that's unclear or that could be interpreted in more than one way. +3. **Ask.** Raise every consequential question now, batched together — don't pick an interpretation silently, and don't defer the question into the work itself. + +Throughout: + +- If multiple interpretations exist, present them — don't choose one silently. +- If a simpler approach exists, say so. Push back when warranted. +- Don't invent APIs. Confirm a function, flag, or endpoint exists — and check its signature — before calling it. If you can't verify it, say so rather than guessing. + +Only once these questions are resolved do you move to execution (§4). + +## 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +## 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: + +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you spot problems outside your task — dead code, bugs — mention them; don't fix or delete them unless asked. + +When your changes create orphans: + +- Remove imports/variables/functions that YOUR changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: every changed line should trace directly to the user's request. + +## 4. Goal-Driven Execution + +**Define success criteria. Loop until verified.** + +Begin only once §1's questions are resolved. Then turn the task into a verifiable goal with an observable check. A passing test is the strongest check; a clean typecheck, running it and inspecting output, or diffing against expected output also count. + +- "Add validation" → "Write tests for invalid inputs, then make them pass" +- "Fix the bug" → "Write a test that reproduces it, then make it pass" +- "Refactor X" → "Ensure tests pass before and after" +- "Update the config" → "Apply it, start the service, and confirm the setting takes effect" + +For multi-step tasks, state a brief plan: + +``` +1. [Step] → verify: [check] +2. [Step] → verify: [check] +3. [Step] → verify: [check] +``` + +When no obvious check exists (docs, exploration, some one-off scripts), ask how to verify success before starting. Don't report success you haven't actually observed. + +Strong, agreed-upon criteria — settled in §1 — let you loop independently. Weak criteria ("make it work") force constant clarification, which is why §1 comes first. + +## 5. Delegate Grunt Work Down a Tier + +**Spend the capable model on judgment; push mechanical, low-context work to a cheaper model.** + +Act as an orchestrator. Fan out grunt work — reading files, gathering context, simple searches, minor mechanical edits, other simple tool calls — to cheaper agents, then review their findings and any code changes yourself before acting on them. Keep the important and dangerous work — architecture, ambiguous decisions, risky or hard-to-reverse edits, final review — on the more capable model. + +Which tier to spawn depends on who you are: + +- **If you are Fable 5**, spawn Opus 4.8 agents for grunt work that still needs some capability; for genuinely simple tasks, hand off directly to Sonnet 5 instead. Review what they return. +- **If you are Opus 4.8**, and a task needs little knowledge or context, fan out to Sonnet 5 agents and review what they return. + +Match the model to the task: pick the cheapest tier that can do the job well, and skip an intermediate tier when the work is simple enough for a lower one. + +Guidelines: + +- Only delegate work that is genuinely low-context and low-risk. If doing it well requires the capable model's judgment, keep it. +- Give each agent a self-contained task with clear success criteria (per §4) so you can verify its output without redoing the work. +- Never merge a delegated finding or edit unblocked — review it first. The cheaper model did the legwork; you own the decision. \ No newline at end of file diff --git a/src/LogExpert.Resources/Resources.Designer.cs b/src/LogExpert.Resources/Resources.Designer.cs index 15e879d7..c73c3534 100644 --- a/src/LogExpert.Resources/Resources.Designer.cs +++ b/src/LogExpert.Resources/Resources.Designer.cs @@ -1649,6 +1649,15 @@ public static string LogExpert_Common_UI_Button_Save { } } + /// + /// Looks up a localized string similar to Could not copy to the clipboard. It is in use by another application. Please try again.. + /// + public static string LogExpert_Common_UI_Message_ClipboardInUse { + get { + return ResourceManager.GetString("LogExpert_Common_UI_Message_ClipboardInUse", resourceCulture); + } + } + /// /// Looks up a localized string similar to Deserialize. /// diff --git a/src/LogExpert.Resources/Resources.de.resx b/src/LogExpert.Resources/Resources.de.resx index 37a08534..3af85639 100644 --- a/src/LogExpert.Resources/Resources.de.resx +++ b/src/LogExpert.Resources/Resources.de.resx @@ -188,6 +188,9 @@ images\png\48\Folder_open.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + Kopieren in die Zwischenablage nicht möglich. Sie wird von einer anderen Anwendung verwendet. Bitte versuchen Sie es erneut. + LogExpert diff --git a/src/LogExpert.Resources/Resources.resx b/src/LogExpert.Resources/Resources.resx index 0ccd9002..9e7e5919 100644 --- a/src/LogExpert.Resources/Resources.resx +++ b/src/LogExpert.Resources/Resources.resx @@ -188,6 +188,9 @@ images\png\48\Folder_open.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + Could not copy to the clipboard. It is in use by another application. Please try again. + LogExpert LogExpert diff --git a/src/LogExpert.Resources/Resources.zh-CN.resx b/src/LogExpert.Resources/Resources.zh-CN.resx index b9031f65..732b85fb 100644 --- a/src/LogExpert.Resources/Resources.zh-CN.resx +++ b/src/LogExpert.Resources/Resources.zh-CN.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 无法复制到剪贴板。剪贴板正被其他应用程序使用,请重试。 + LogExpert LogExpert diff --git a/src/LogExpert.Tests/Extensions/ClipboardHelperTests.cs b/src/LogExpert.Tests/Extensions/ClipboardHelperTests.cs new file mode 100644 index 00000000..310d793e --- /dev/null +++ b/src/LogExpert.Tests/Extensions/ClipboardHelperTests.cs @@ -0,0 +1,117 @@ +using LogExpert.UI.Extensions; + +using NUnit.Framework; + +using Vanara.PInvoke; + +namespace LogExpert.Tests.Extensions; + +/// +/// Tests for the hardened clipboard writes (see issue with "Requested Clipboard operation +/// did not succeed" / ExternalException, previously reported in #195). When another +/// application holds the clipboard open, LogExpert must report failure instead of crashing. +/// +[TestFixture] +[Apartment(ApartmentState.STA)] +[NonParallelizable] +[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Tests")] +public class ClipboardHelperTests +{ + [Test] + public void TrySetText_ClipboardAvailable_PlacesTextAndReturnsTrue () + { + var ok = ClipboardHelper.TrySetText("LogExpert clipboard test"); + + Assert.That(ok, Is.True); + Assert.That(GetClipboardTextWithRetry(), Is.EqualTo("LogExpert clipboard test")); + } + + /// + /// Reads the clipboard text, retrying briefly. Clipboard managers and similar tools + /// open the clipboard to inspect new content right after it changes, which can make an + /// immediate read-back fail or come up empty even though the write succeeded. + /// + private static string GetClipboardTextWithRetry () + { + for (var i = 0; i < 20; i++) + { + var text = Clipboard.GetText(); + if (!string.IsNullOrEmpty(text)) + { + return text; + } + + Thread.Sleep(100); + } + + return string.Empty; + } + + [Test] + public void TrySetText_ClipboardHeldByAnotherWindow_ReturnsFalseInsteadOfThrowing () + { + using ClipboardLock clipboardLock = new(); + + var ok = ClipboardHelper.TrySetText("some text"); + + Assert.That(ok, Is.False); + } + + [Test] + public void TrySetDataObject_ClipboardAvailable_PlacesDataAndReturnsTrue () + { + var ok = ClipboardHelper.TrySetDataObject("LogExpert data object test"); + + Assert.That(ok, Is.True); + Assert.That(GetClipboardTextWithRetry(), Is.EqualTo("LogExpert data object test")); + } + + [Test] + public void TrySetDataObject_ClipboardHeldByAnotherWindow_ReturnsFalseInsteadOfThrowing () + { + using ClipboardLock clipboardLock = new(); + + var ok = ClipboardHelper.TrySetDataObject("some data"); + + Assert.That(ok, Is.False); + } + + /// + /// Holds the Win32 clipboard open from a background thread (without closing it), + /// which makes every clipboard access in other threads/processes fail — the same + /// situation an external clipboard-monitoring tool causes. + /// + private sealed class ClipboardLock : IDisposable + { + private readonly Thread _thread; + private readonly ManualResetEventSlim _acquired = new(false); + private readonly ManualResetEventSlim _release = new(false); + + public ClipboardLock () + { + _thread = new Thread(() => + { + if (!User32.OpenClipboard(HWND.NULL)) + { + throw new InvalidOperationException("Test setup failed: could not open the clipboard"); + } + + _acquired.Set(); + _release.Wait(); + _ = User32.CloseClipboard(); + }) + { + IsBackground = true + }; + + _thread.Start(); + _ = _acquired.Wait(TimeSpan.FromSeconds(5)); + } + + public void Dispose () + { + _release.Set(); + _ = _thread.Join(TimeSpan.FromSeconds(5)); + } + } +} diff --git a/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs b/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs index c433cb3d..206ce17b 100644 --- a/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs +++ b/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs @@ -1967,9 +1967,9 @@ private void OnEditModeCopyToolStripMenuItemClick (object sender, EventArgs e) { if (dataGridView.EditingControl is DataGridViewTextBoxEditingControl ctl) { - if (!string.IsNullOrEmpty(ctl.SelectedText)) + if (!string.IsNullOrEmpty(ctl.SelectedText) && !ClipboardHelper.TrySetText(ctl.SelectedText)) { - Clipboard.SetText(ctl.SelectedText); + StatusLineError(Resources.LogExpert_Common_UI_Message_ClipboardInUse); } } } @@ -5370,7 +5370,10 @@ private void CopyMarkedLinesToClipboard () data = new DataObject(DataFormats.UnicodeText, transformed); } - Clipboard.SetDataObject(data); + if (!ClipboardHelper.TrySetDataObject(data)) + { + StatusLineError(Resources.LogExpert_Common_UI_Message_ClipboardInUse); + } } else { @@ -5412,7 +5415,10 @@ private void CopyMarkedLinesToClipboard () } } - Clipboard.SetDataObject(clipText.ToString()); + if (!ClipboardHelper.TrySetDataObject(clipText.ToString())) + { + StatusLineError(Resources.LogExpert_Common_UI_Message_ClipboardInUse); + } } } diff --git a/src/LogExpert.UI/Dialogs/ExceptionWindow.cs b/src/LogExpert.UI/Dialogs/ExceptionWindow.cs index 7e283d33..b8bd96ef 100644 --- a/src/LogExpert.UI/Dialogs/ExceptionWindow.cs +++ b/src/LogExpert.UI/Dialogs/ExceptionWindow.cs @@ -1,5 +1,7 @@ using System.Runtime.Versioning; +using LogExpert.UI.Extensions; + namespace LogExpert.UI.Dialogs; [SupportedOSPlatform("windows")] @@ -28,7 +30,7 @@ public ExceptionWindow (string errorText, string stackTrace) _errorText = errorText; _stackTrace = stackTrace; - stackTraceTextBox.Text = _errorText + @"\n\n" + _stackTrace; + stackTraceTextBox.Text = _errorText + Environment.NewLine + Environment.NewLine + _stackTrace; stackTraceTextBox.Select(0, 0); ResumeLayout(); @@ -51,7 +53,8 @@ private void ApplyResources () private void CopyToClipboard () { - Clipboard.SetText(_errorText + @"\n\n" + _stackTrace); + // Never let a failed clipboard access escalate out of the error dialog itself + _ = ClipboardHelper.TrySetText(_errorText + Environment.NewLine + Environment.NewLine + _stackTrace); } #endregion diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs index 7fa3260d..3ad5a813 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs @@ -2417,8 +2417,15 @@ private void OnToolStripButtonBubblesClick (object sender, EventArgs e) [SupportedOSPlatform("windows")] private void OnCopyPathToClipboardToolStripMenuItemClick (object sender, EventArgs e) { - var logWindow = dockPanel.ActiveContent as LogWindow.LogWindow; - Clipboard.SetText(logWindow.Title); + if (dockPanel.ActiveContent is not LogWindow.LogWindow logWindow) + { + return; + } + + if (!ClipboardHelper.TrySetText(logWindow.Title)) + { + _ = MessageBox.Show(this, Resources.LogExpert_Common_UI_Message_ClipboardInUse, Resources.LogExpert_Common_UI_Title_LogExpert, MessageBoxButtons.OK, MessageBoxIcon.Warning); + } } private void OnFindInExplorerToolStripMenuItemClick (object sender, EventArgs e) diff --git a/src/LogExpert.UI/Dialogs/PluginHashDialog.cs b/src/LogExpert.UI/Dialogs/PluginHashDialog.cs index 6d6f5c02..35fd1112 100644 --- a/src/LogExpert.UI/Dialogs/PluginHashDialog.cs +++ b/src/LogExpert.UI/Dialogs/PluginHashDialog.cs @@ -1,7 +1,8 @@ using System.Globalization; -using System.Runtime.InteropServices; using System.Runtime.Versioning; +using LogExpert.UI.Extensions; + namespace LogExpert.UI.Dialogs; [SupportedOSPlatform("windows")] @@ -55,25 +56,17 @@ private void ApplyResources (string pluginName) private void OnButtonCopyClick (object sender, EventArgs e) { - try - { - Clipboard.SetText(_hash); - _ = MessageBox.Show( + _ = ClipboardHelper.TrySetText(_hash) + ? MessageBox.Show( Resources.PluginHashDialog_UI_Message_CopySuccess, Resources.PluginHashDialog_UI_Message_SuccessTitle, MessageBoxButtons.OK, - MessageBoxIcon.Information); - } - catch (Exception ex) when (ex is ExternalException or - ThreadStateException or - ThreadStateException) - { - _ = MessageBox.Show( - string.Format(CultureInfo.InvariantCulture, Resources.PluginHashDialog_UI_Message_CopyError, ex.Message), + MessageBoxIcon.Information) + : MessageBox.Show( + string.Format(CultureInfo.InvariantCulture, Resources.PluginHashDialog_UI_Message_CopyError, Resources.LogExpert_Common_UI_Message_ClipboardInUse), Resources.PluginHashDialog_UI_Message_ErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - } } private void OnButtonCloseClick (object sender, EventArgs e) diff --git a/src/LogExpert.UI/Extensions/ClipboardHelper.cs b/src/LogExpert.UI/Extensions/ClipboardHelper.cs new file mode 100644 index 00000000..e0a281a7 --- /dev/null +++ b/src/LogExpert.UI/Extensions/ClipboardHelper.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace LogExpert.UI.Extensions; + +/// +/// Hardened clipboard writes. Windows clipboard access fails with an +/// when another application holds the clipboard open +/// (clipboard managers, RDP, Office, ...) — even after the WinForms-internal retries. +/// These helpers report failure instead of letting the exception crash the UI. +/// +[SupportedOSPlatform("windows")] +internal static class ClipboardHelper +{ + public static bool TrySetText (string text) + { + try + { + Clipboard.SetText(text); + return true; + } + catch (ExternalException) + { + return false; + } + } + + public static bool TrySetDataObject (object data) + { + try + { + // copy: true keeps the data available after LogExpert exits (same as SetText) + Clipboard.SetDataObject(data, copy: true); + return true; + } + catch (ExternalException) + { + return false; + } + } +} diff --git a/src/PluginRegistry/PluginHashGenerator.Generated.cs b/src/PluginRegistry/PluginHashGenerator.Generated.cs index bc30d39e..a442ef7d 100644 --- a/src/PluginRegistry/PluginHashGenerator.Generated.cs +++ b/src/PluginRegistry/PluginHashGenerator.Generated.cs @@ -10,7 +10,7 @@ public static partial class PluginValidator { /// /// Gets pre-calculated SHA256 hashes for built-in plugins. - /// Generated: 2026-06-28 09:35:15 UTC + /// Generated: 2026-07-09 10:00:39 UTC /// Configuration: Release /// Plugin count: 21 /// @@ -18,27 +18,27 @@ public static Dictionary GetBuiltInPluginHashes() { return new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["AutoColumnizer.dll"] = "ECB41011CE6D968C9DCF6264147E4DEEBB5E074C248DB234C982491241977EDD", + ["AutoColumnizer.dll"] = "9AE096A54D4B084266B4F7453887D03DB80FFD0EB4188982EA7CA39D5749DAE2", ["BouncyCastle.Cryptography.dll"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", ["BouncyCastle.Cryptography.dll (x86)"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", - ["CsvColumnizer.dll"] = "DBA5875075DA4B3D9DB472974238BBAC7F6E5D167CDB194D968E6776B5093763", - ["CsvColumnizer.dll (x86)"] = "DBA5875075DA4B3D9DB472974238BBAC7F6E5D167CDB194D968E6776B5093763", - ["DefaultPlugins.dll"] = "390C8FFA3DD9E02FBB3BC8EC564A33EAA1A2BF4E7D4908B550DEE5D98BE5D3B4", - ["FlashIconHighlighter.dll"] = "C1AEEF8357CC8373FCB583805299959BCBE408144389016E86177568D55AF879", - ["GlassfishColumnizer.dll"] = "B1AD18EFBBC2DAC96B28B118045334CB52B8CC36850B6212046E25FDEF65BBAC", - ["JsonColumnizer.dll"] = "A18B94F29841AB94CD8F4DE580959FF79AB889B5F7FC9D0ACA7A019D5C9293AF", - ["JsonCompactColumnizer.dll"] = "65F3EF5CC1A21B0946206CE6A72B5CFD1C1E3F773CA3BD2FEE93751F74B5C10E", - ["Log4jXmlColumnizer.dll"] = "20C9AB6C84C3D0B6D7AAF65475070061C2F3F2356162AF01D5C9177DEA3108FE", - ["LogExpert.Resources.dll"] = "1E795DD51B19A5E917934BBB3D70990362BF0C37D2976146F3FDA216506C81AD", + ["CsvColumnizer.dll"] = "21E681DD238038F026E79DD15BE9A34158D7C087BD60E7D8893BB710E5D7F566", + ["CsvColumnizer.dll (x86)"] = "21E681DD238038F026E79DD15BE9A34158D7C087BD60E7D8893BB710E5D7F566", + ["DefaultPlugins.dll"] = "8ED4B39B5A54A43A8C74BF6AF88D042C36B276EF13B8F670F12A7BE7B621A657", + ["FlashIconHighlighter.dll"] = "F2AED996E9F56F91014E9CFA2DC94F5F69FD6F365326382A7EBACA119770F224", + ["GlassfishColumnizer.dll"] = "571DCF5E49E39ABB8A439C7AE19DA58088E4ADBBEDE07B20D97AB983AE2E982D", + ["JsonColumnizer.dll"] = "A5161C33CA4912456CDBDEC742ADFDDFF592F15C80655B6F89BC8C322E77A395", + ["JsonCompactColumnizer.dll"] = "7DBC4C1C715AD8ADC32297AE415D50741CBE949E4180F87ACBA4DB4FFB491A96", + ["Log4jXmlColumnizer.dll"] = "BD3C1D71967A364C4666B3CDA4738C85677E42800C6364BC03768507F57FB624", + ["LogExpert.Resources.dll"] = "F7C7E2291D4CDB72C775B08D8E33637C1681B9557D24D3F214A8D3EFBDDEF875", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll (x86)"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.Logging.Abstractions.dll"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", ["Microsoft.Extensions.Logging.Abstractions.dll (x86)"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", - ["RegexColumnizer.dll"] = "B53EFBBB900FDEFB4757FDAB44E44985BBC99841F55FA6B2F44F646A093A1901", - ["SftpFileSystem.dll"] = "E8020D131D977B3A14768FA166D7BB11CDFDBA96AFBEDA5D86B0FC238F8386FA", - ["SftpFileSystem.dll (x86)"] = "A45F6EA1E9B39551361A9859D65A58A84E2A7862547679D7D63E9E3001AAB335", - ["SftpFileSystem.Resources.dll"] = "508D973F0ED43849088D84CC48ABE869C323F8BA878C5F022AE3C60CB4835B38", - ["SftpFileSystem.Resources.dll (x86)"] = "508D973F0ED43849088D84CC48ABE869C323F8BA878C5F022AE3C60CB4835B38", + ["RegexColumnizer.dll"] = "456A2DBC538DCA15EB8135AE1D4CD990CEF07718331C281789ECDE6591C730B7", + ["SftpFileSystem.dll"] = "E0E304E9D1C957CF4E4A0C2EBD7C2D78F96A4D9133779E7E06AA70820DBDFCF2", + ["SftpFileSystem.dll (x86)"] = "E945D67FB8034C5FADE385EE76BBCDE3D09AF5322CE5D8B36E2B00E81B77728B", + ["SftpFileSystem.Resources.dll"] = "860BB8CC374983BDA9D0BEC8CE6A7E227FC9AA7DC68F47E21608C8BCD15E13ED", + ["SftpFileSystem.Resources.dll (x86)"] = "860BB8CC374983BDA9D0BEC8CE6A7E227FC9AA7DC68F47E21608C8BCD15E13ED", }; }