From df21340402f2e24375a99277713bbe60a9a4e51d Mon Sep 17 00:00:00 2001 From: mirivlad Date: Tue, 16 Jun 2026 02:22:44 +0800 Subject: [PATCH] chore(frontend): add diagnostics before app split - main.js: global error handlers (error + unhandledrejection), try/catch around App mount with inline fallback UI - bindings_logging.go: FrontendLog() and LogStartupStep() backend bindings for persistent application logging - App.js: FrontendLog() and LogStartupStep() Wails JS bindings - docs/frontend-architecture.md: component architecture, state ownership rules, communication patterns, logging guide, troubleshooting All existing code untouched. This is a pure additive step that enables blank-window debugging before any App.svelte extraction begins. --- cmd/verstak-gui/bindings_logging.go | 89 +++++++++ .../{main-CzfuqGWF.js => main-Da3BSkUM.js} | 2 +- cmd/verstak-gui/frontend-dist/index.html | 2 +- docs/frontend-architecture.md | 178 ++++++++++++++++++ frontend/src/main.js | 47 ++++- frontend/src/wailsjs/go/main/App.js | 22 ++- 6 files changed, 335 insertions(+), 5 deletions(-) create mode 100644 cmd/verstak-gui/bindings_logging.go rename cmd/verstak-gui/frontend-dist/assets/{main-CzfuqGWF.js => main-Da3BSkUM.js} (99%) create mode 100644 docs/frontend-architecture.md diff --git a/cmd/verstak-gui/bindings_logging.go b/cmd/verstak-gui/bindings_logging.go new file mode 100644 index 0000000..1cc4c2b --- /dev/null +++ b/cmd/verstak-gui/bindings_logging.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + "verstak/internal/core/config" +) + +// appLogPath returns the path to the application log file. +// Uses ~/.local/state/verstak/logs/verstak.log on Linux, +// or falls back to ~/.config/verstak/logs/verstak.log. +func appLogPath() string { + // Prefer XDG_STATE_HOME, then fallback to config dir + stateDir := os.Getenv("XDG_STATE_HOME") + if stateDir == "" { + home, err := os.UserHomeDir() + if err == nil { + stateDir = filepath.Join(home, ".local", "state") + } + } + if stateDir != "" { + dir := filepath.Join(stateDir, "verstak", "logs") + if err := os.MkdirAll(dir, 0o755); err == nil { + return filepath.Join(dir, "verstak.log") + } + } + // Fallback to config dir + cfgDir, err := config.EnsureConfigDir() + if err != nil { + return "" + } + dir := filepath.Join(cfgDir, "logs") + if err := os.MkdirAll(dir, 0o755); err != nil { + return "" + } + return filepath.Join(dir, "verstak.log") +} + +// appLog writes a timestamped line to the application log file. +func appLog(level, msg string) { + logPath := appLogPath() + if logPath == "" { + log.Printf("[%s] %s", level, msg) + return + } + line := fmt.Sprintf("[%s] [%s] %s\n", time.Now().Format("2006-01-02T15:04:05"), level, msg) + f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + log.Printf("[%s] %s", level, msg) + return + } + defer f.Close() + f.WriteString(line) +} + +// FrontendLog receives log messages from the frontend runtime. +// Called from JS via Wails binding. Works even if vault is not open. +// level: "info", "warn", "error" +// message: human-readable message +// stack: JS stack trace (optional, only for errors) +func (a *App) FrontendLog(level, message, stack string) { + msg := "[frontend] " + message + if stack != "" { + msg += "\n stack: " + stack + } + // Always log to Go's standard logger (visible in dev mode console) + log.Printf("[frontend][%s] %s", level, message) + // Persist to log file + appLog("frontend-"+level, msg) +} + +// LogStartupStep logs a startup diagnostic step. +// Called from Go side to trace initialization progress. +func (a *App) LogStartupStep(step string, success bool, detail string) { + status := "ok" + if !success { + status = "fail" + } + msg := fmt.Sprintf("[startup] %s: %s", step, status) + if detail != "" { + msg += " — " + detail + } + log.Print(msg) + appLog("startup", msg) +} diff --git a/cmd/verstak-gui/frontend-dist/assets/main-CzfuqGWF.js b/cmd/verstak-gui/frontend-dist/assets/main-Da3BSkUM.js similarity index 99% rename from cmd/verstak-gui/frontend-dist/assets/main-CzfuqGWF.js rename to cmd/verstak-gui/frontend-dist/assets/main-Da3BSkUM.js index 17e905a..d844e66 100644 --- a/cmd/verstak-gui/frontend-dist/assets/main-CzfuqGWF.js +++ b/cmd/verstak-gui/frontend-dist/assets/main-Da3BSkUM.js @@ -84,4 +84,4 @@ https://github.com/highlightjs/highlight.js/issues/2277`),J=G,B=D),j===void 0&&( `,dirty:!1})}}function jo(p){dn({title:v("common.delete"),message:v("note.deleteConfirm",{title:p.title})||`Удалить заметку «${p.title}»?`,confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("DeleteNote",p.id),t(36,te=te.filter(O=>O.id!==p.id)),ne&&ne.id===p.id&&t(37,ne=null);try{await mi()}catch{}}catch(O){t(34,B=String(O))}}})}function Uo(){if(ne&&ne.dirty){dn({title:v("note.unsavedTitle"),message:v("note.unsavedMessage"),confirmText:v("note.unsavedClose"),danger:!1,onConfirm:()=>{t(37,ne=null),t(39,Ae="edit")}});return}t(37,ne=null),t(39,Ae="edit")}async function Yp(){if(ne)try{await ae("SaveNote",ne.id,ne.content),t(37,ne.dirty=!1,ne)}catch{t(37,ne.dirty=!1,ne)}}function Xp(){t(41,qe=""),t(42,Ne=""),t(40,ze=!0)}function ci(){if(!ne){t(40,ze=!1);return}const p=qe.trim()||Ne.trim()||"link",O=Ne.trim()||"#",se=`[${p}](${O})`;t(37,ne.content=ne.content+(ne.content.endsWith(` `)?"":` `)+se+` -`,ne),t(37,ne.dirty=!0,ne),t(40,ze=!1)}function Jp(){t(43,ee=!0)}function Qp(p){const O=p.detail.markdown;de&&typeof de.insertText=="function"&&de.insertText(O),t(43,ee=!1)}function xp(){t(43,ee=!1)}function ui(p,O){t(46,Be=p),t(47,ge=O),We=O}function Gn(){t(46,Be=null),t(47,ge=""),We=""}async function fi(){if(!Be||!ge.trim()){Gn();return}if(ge.trim()===We){Gn();return}try{await ae("RenameNote",Be,ge.trim()),t(36,te=te.map(p=>p.id===Be?{...p,title:ge.trim()}:p)),ne&&ne.id===Be&&t(37,ne.title=ge.trim(),ne),Gn()}catch(p){t(34,B=String(p)),Gn()}}async function $p(p){const{type:O,id:se}=p.detail;if(se)switch(O){case"case":await em(se);break;case"note":await tm(se);break;case"file":await nm(se);break;case"secret":yn(v("note.internal.secretNotImplemented"));break;default:yn(`verstak://${O}/${se}`)}}async function em(p){try{const O=await ae("GetNodeDetail",p);if(!O){yn(v("note.internal.caseNotFound"));return}await Pt(O)}catch{yn(v("note.internal.caseNotFound"))}}async function tm(p){try{const O=await ae("GetNodeDetail",p);if(!O){yn(v("note.internal.noteNotFound"));return}const se=O.parent_id||O.parentId||"";if(se){const De=await ae("GetNodeDetail",se);De&&await Pt(De)}Qt("notes");const _e=te.find(De=>De.id===p);_e?await wn(_e):await wn({id:O.id,title:O.title})}catch{yn(v("note.internal.noteNotFound"))}}async function nm(p){try{const O=await ae("GetNodeDetail",p);if(!O){yn(v("note.internal.fileNotFound"));return}const se=O.parent_id||O.parentId||"";if(se){const _e=await ae("GetNodeDetail",se);_e&&await Pt(_e),Qt("files"),await yt(se);const De=gt.find(wt=>wt.id===p);De?await ll(De):yn(v("note.internal.fileFound",{title:O.title}))}else yn(v("note.internal.fileFound",{title:O.title}))}catch{yn(v("note.internal.fileNotFound"))}}function yn(p){t(45,Se=p),t(44,re=!0),setTimeout(()=>{t(44,re=!1)},3e3)}function lm(p){ne&&(t(37,ne.content=p.detail.content,ne),t(37,ne.dirty=!0,ne))}function sm(p){t(39,Ae=p.detail.mode)}function im(){Xp()}function om(){Jp()}function ws(p=null){t(57,ye=p),t(58,x=null),t(51,Ce=p?p.summary:""),t(52,je=p?String(p.minutes||""):""),t(53,Ue=p&&p.date||""),t(54,Pe=p&&p.details||""),t(55,Fe=p?!!p.billable:!1),t(56,ue=p?!!p.approximate:!1),t(50,Oe=!0)}function Ho(){t(50,Oe=!1),t(57,ye=null),t(58,x=null)}async function di(p=""){const O=p||(X?X.id:"");X&&X.id===O&&t(49,it=ji(await ae("ListWorklog",X.id))||it),Z==="journal"&&await rl()}async function rm(){const p=parseInt(je,10);if(!(!Ce.trim()||isNaN(p)||p<=0)&&!(!x&&!ye&&!X)){try{if(x){const O=JSON.stringify(Ds(x));await ae("AcceptSuggestionFull",x.nodeId,Ce.trim(),Pe,Ue,p,ue,Fe,O),await ys()}else ye?(await ae("UpdateWorklogEntry",ye.id,Ce.trim(),Pe,Ue,p,ue,Fe),await di(ye.nodeId)):(await ae("CreateWorklogFull",X.id,Ce.trim(),Pe,Ue,p,ue,Fe),await di(X.id))}catch(O){t(34,B=String(O));return}t(50,Oe=!1),t(57,ye=null),t(58,x=null)}}function pi(p){dn({title:v("worklog.deleteEntry"),message:v("worklog.deleteConfirm"),confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("DeleteWorklogEntry",p.id),await di(p.nodeId)}catch(O){t(34,B=String(O))}}})}async function ys(){t(59,pe=await ae("GetSuggestions")||[]),t(60,me=pe.length),X&&t(49,it=ji(await ae("ListWorklog",X.id))),Z==="journal"&&await rl()}async function ol(){t(7,Ht=await ae("ListTrash")||{nodes:[],entries:[],trashPath:"",count:0}),t(74,El=Ht.count||0);const p=Ht.nodes||[];t(75,Mt=Mt.filter(O=>p.some(se=>se.id===O))),Wt&&!p.some(O=>O.id===Wt)?li():_t.length>0&&(t(264,_t=_t.filter(O=>p.some(se=>se.id===O.id))),t(8,Wt=_t.length>0?_t[_t.length-1].id:""))}async function mi(){try{t(74,El=await ae("TrashCount")||0)}catch{t(74,El=0)}}function am(){return Wt&&(((Ht==null?void 0:Ht.nodes)||[]).find(p=>p.id===Wt)||_t[_t.length-1])||null}function Al(p){Wn(),t(8,Wt=p.id);const O=_t.findIndex(se=>se.id===p.id);O>=0?t(264,_t=_t.slice(0,O+1)):t(264,_t=[..._t,{id:p.id,title:p.title}]),t(75,Mt=[])}function cm(){if(_t.length===0)return;Wn();const p=_t.slice(0,-1);t(264,_t=p),t(8,Wt=p.length>0?p[p.length-1].id:""),t(75,Mt=[])}async function Yl(p){t(99,Rt={name:p.title,type:"file",mime:"text/plain",size:0,fileId:p.id}),t(100,nn=""),t(102,Rn=""),t(101,el=!0);try{p.trashFsPath?t(100,nn=await ae("ReadTrashFile",p.trashFsPath)||""):t(100,nn=await ae("ReadTrashFileContent",p.id)||"");const O=(p.title||"").split(".").pop().toLowerCase();["png","jpg","jpeg","gif","webp","bmp","svg"].includes(O)&&t(100,nn="data:image/"+(O==="svg"?"svg+xml":O)+";base64,"+btoa(nn))}catch(O){t(102,Rn=String(O))}t(101,el=!1)}function Wo(p){t(75,Mt=Mt.includes(p)?Mt.filter(O=>O!==p):[...Mt,p])}function gi(p){return Mt.length>0?Mt:[p]}async function hi(p){try{await ae("RestoreTrashNodesJSON",JSON.stringify(p)),await il(),await ol()}catch(O){t(34,B=String(O))}}async function _i(p){dn({title:v("delete.confirmTitle"),message:v("delete.confirmMessage")+" "+p.length+"?",confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("PurgeTrashNodesJSON",JSON.stringify(p)),await ol()}catch(O){t(34,B=String(O))}}})}async function um(){dn({title:v("delete.confirmTitle"),message:v("trash.empty")+"?",confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("EmptyTrash"),await ol()}catch(p){t(34,B=String(p))}}})}function Es(p){t(58,x=p),t(57,ye=null),t(51,Ce=p.summary||""),t(52,je=String(p.suggestedMin||"")),t(53,Ue=""),t(54,Pe=""),t(55,Fe=!1),t(56,ue=!0),t(50,Oe=!0)}async function Ts(p){try{await ae("DismissSuggestion",p.nodeId,JSON.stringify(Ds(p))),await ys()}catch(O){t(34,B=String(O))}}async function fm(){t(64,rt=!0);try{const p=await ae("ListBrowserEvents","pending",50,0);t(63,Je=p||[])}catch(p){console.warn("[browser] load error:",p)}finally{t(64,rt=!1)}}async function dm(p){await ae("AcceptBrowserEvent",p.id,""),t(63,Je=Je.filter(O=>O.id!==p.id))}async function pm(p){await ae("DismissBrowserEvent",p.id),t(63,Je=Je.filter(O=>O.id!==p.id))}async function mm(p){X&&X.id?(await ae("AttachBrowserEventToNode",p.id,X.id),t(63,Je=Je.filter(O=>O.id!==p.id))):(await ae("DismissBrowserEvent",p.id),t(63,Je=Je.filter(O=>O.id!==p.id)))}async function bi(p){try{const O=Ds(p),se=JSON.stringify(O);Ps("acceptTodaySuggestion: nodeId="+p.nodeId+" eventIds="+se+" events="+JSON.stringify((p.events||[]).map(_e=>({id:_e.id,type:_e.eventType,title:_e.title})))),await ae("AcceptSuggestionWith",p.nodeId,p.summary,p.suggestedMin,"",se),await ys()}catch(O){Ps("acceptTodaySuggestion error: "+O)}}async function Go(p){try{const O=Ds(p),se=JSON.stringify(O);Ps("acceptJournalSuggestion: nodeId="+p.nodeId+" eventIds="+se+" events="+JSON.stringify((p.events||[]).map(_e=>({id:_e.id,type:_e.eventType,title:_e.title})))),await ae("AcceptSuggestionWith",p.nodeId,p.summary,p.suggestedMin,"",se),await ys()}catch(O){Ps("acceptJournalSuggestion error: "+O)}}async function rl(){try{const[p,O,se]=await Promise.all([ae("ListWorklogReport",I,M,L,R,z,U),ae("WorklogReportSummary",I,M,L,R,z,U),ae("GetSuggestions")]);if(t(18,N=(p||[]).map(_e=>(_e._expanded===void 0&&(_e._expanded=!1),_e))),t(19,A=O||null),t(59,pe=se||[]),t(60,me=pe.length),L&&!W)if(p&&p.length>0&&p[0].nodePath)t(26,W=p[0].nodePath);else try{t(26,W=await ae("GetNodeTitle",L))}catch{t(26,W="")}}catch{t(18,N=[]),t(19,A=null),t(59,pe=[]),t(60,me=0)}}async function Ss(p){try{const O=await ae("SaveWorklogReport",p,I,M,L,R,z,U);t(27,F=O),setTimeout(()=>t(27,F=""),4e3)}catch(O){if(String(O).includes("отменено"))return;t(27,F="Ошибка: "+String(O)),setTimeout(()=>t(27,F=""),6e3)}}let Ko;async function vi(p){if(p._expanded=!p._expanded,t(18,N),p._expanded&&!p._events&&p._hasEvents===void 0)try{p._events=await ae("GetWorklogEntryEvents",p.id)||[],p._hasEvents=p._events.length>0,t(18,N)}catch{p._events=[],p._hasEvents=!1,t(18,N)}}async function ki(p){if(p._expanded=!p._expanded,p._expanded&&p.source==="suggestion"&&!p._events&&p._hasEvents===void 0)try{p._events=await ae("GetWorklogEntryEvents",p.id)||[],p._hasEvents=p._events.length>0}catch(O){O._events=[],O._hasEvents=!1}t(49,it)}async function gm(){const p=V.trim();if(!p||p.length<2){t(30,Y=[]),t(31,G=!1);return}try{t(30,Y=await ae("SearchNodes",p)||[]),t(31,G=Y.length>0)}catch{t(30,Y=[]),t(31,G=!1)}}function hm(){clearTimeout(Ko),Ko=setTimeout(gm,200)}function Vo(p){t(23,L=p.id),t(26,W=p.path||p.title),t(22,R=!0),t(29,V=""),t(30,Y=[]),t(31,G=!1),rl()}function qo(){t(23,L=""),t(22,R=!1),t(26,W=""),t(29,V=""),t(30,Y=[]),t(31,G=!1),rl()}function _m(){t(20,I=""),t(21,M=""),t(23,L=""),t(22,R=!1),t(24,z="all"),t(25,U="all"),t(26,W=""),t(29,V=""),t(30,Y=[]),t(31,G=!1),rl()}async function Zo(){const p=await ae("PickFile");if(!p)return;const O=bt||X.id;await Yo(O,p)}async function bm(){const p=await ae("PickDirectory");if(!p)return;const O=bt||X.id;await Yo(O,p)}async function Yo(p,O){t(91,fn=!0);try{const se=await ae("PreviewImport",O);t(92,Sl=se),Cl=O,Vl=p,t(93,xn=!0)}catch(se){t(34,B=String(se))}t(91,fn=!1)}async function wi(p){try{const O=Vl||X.id,se=p==="copy"?await ae("AddPathCopy",O,Cl):await ae("AddPathLink",O,Cl);t(93,xn=!1),t(92,Sl=null),t(97,vt=[]),t(96,bt=null),await Promise.all([vs(O),yt(O),ai(O)])}catch(O){t(34,B=String(O))}}function Xo(){t(93,xn=!1),t(92,Sl=null)}async function Jo({id:p,type:O}){const se=v(O==="folder"?"delete.folder":"delete.file");dn({title:v("delete.confirmTitle"),message:v("delete.confirmMessage")+" "+se+"?",confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("DeleteFileOrFolder",p),mt=mt.filter(De=>De.nodeId!==p);const _e=bt||X.id;await yt(_e)}catch(_e){t(34,B=String(_e))}}})}async function vm(p){try{if(!p||p.length===0)return;await Mm(p,"drop")}finally{Lt()}}function km(p){return{note_created:v("event.noteCreated"),note_updated:v("event.noteUpdated"),note_deleted:v("event.noteDeleted"),file_added:v("event.fileAdded"),file_deleted:v("event.fileDeleted"),file_renamed:v("event.fileRenamed"),file_copied:v("event.fileCopied"),file_moved:v("event.fileMoved"),folder_added:v("event.folderAdded"),folder_deleted:v("event.folderDeleted"),folder_renamed:v("event.folderRenamed"),folder_moved:v("event.folderMoved"),node_created:v("event.caseCreated"),node_updated:v("event.caseUpdated"),node_deleted:v("event.nodeDeleted"),action_created:v("event.actionCreated"),action_done:v("event.actionDone"),worklog_added:v("event.worklogAdded")}[p]||p}function yi(p){return{project:v("kind.project"),client:v("kind.client"),document:v("kind.document"),recipe:v("kind.recipe"),folder:v("kind.folder"),note:v("kind.note"),file:v("kind.file"),archive:v("kind.archive"),case:v("kind.case"),link:v("kind.link")}[p]||p||v("kind.case")}function wm(p){return p==="action"?v("kind.action"):yi(p)}function ym(p){return p?v("capture.kind."+p):""}function Em(p){return p?v("capture.source."+p):""}function Tm(p){const O=[];return p.captureKind&&O.push(ym(p.captureKind)),p.captureSource&&O.push(Em(p.captureSource)),p.captureContextLabel&&O.push(`${v("inbox.capturedIn")}: ${p.captureContextLabel}`),p.suggestedTargetLabel&&O.push(`${v("inbox.suggestedTarget")}: ${p.suggestedTargetLabel}`),O.push(an(p.capturedAt||p.createdAt)),O.filter(Boolean).join(" · ")}function Sm(p){!p||!p.id||(t(3,tt=[p,...tt.filter(O=>O.id!==p.id)]),X&&(p.captureContextNodeId===X.id||p.suggestedTargetNodeId===X.id)&&t(65,Ze=[p,...Ze.filter(O=>O.id!==p.id)]))}function Cm(){return X&&X.id?{contextType:"node",nodeId:X.id,suggestedTargetNodeId:X.id}:Z?{contextType:"section",section:Z}:{contextType:"global",section:"root"}}function al(){return JSON.stringify(Cm())}function Am(){return X&&X.id?v("capture.dropOverlayNode",{title:X.title}):v("capture.dropOverlayGlobal")}async function Nl(p=null){p&&Sm(p),Z==="inbox"&&t(3,tt=await ae("ListInboxNodes")||tt)}async function Nm(p,O){const se=String(p||"").trim();if(!se)return null;const _e=Ui(se),De=_e?await ae("CaptureURLWithContext",_e,"",O,al()):await ae("CaptureTextWithContext",se,O,al());return await Nl(De),De}async function Qo(p,O,se){const _e=String(p||"").trim();if(!_e)return null;const De=await ae("CaptureURLWithContext",_e,O||"",se,al());return await Nl(De),De}async function Rm(p,O){if(!p)return null;const se=p.path||p.webkitRelativePath||"";if(se){const wt=await ae("CapturePathWithContext",se,O,al());return await Nl(wt),wt}const _e=await L5(p),De=await ae("CaptureFileDataWithContext",p.name||`clipboard.${M5(p.type)}`,_e,O,al());return await Nl(De),De}async function Ei(p,O){var Jl,En,kr;if(!p)return!1;let se=!1;const _e=Array.from(p.files||[]);for(const ul of _e)await Rm(ul,O),se=!0;const De=(Jl=p.getData)==null?void 0:Jl.call(p,"text/x-moz-url");if(De){const ul=O5(De),Ql=ul?Ui(ul.url):"";if(Ql)return await Qo(Ql,ul.title,O),!0}const wt=(En=p.getData)==null?void 0:En.call(p,"text/uri-list");if(wt){const ul=D5(wt),Ql=Ui(ul);if(Ql)return await Qo(Ql,"",O),!0}const Il=(kr=p.getData)==null?void 0:kr.call(p,"text/plain");return String(Il||"").trim()&&(await Nm(Il,O),se=!0),se}async function Im(){if(!Ve){t(67,nt=""),t(66,Ve=!0);try{const p=await ae("CaptureClipboardTextWithContext",al());await Nl(p),t(67,nt=v("inbox.captured"))}catch(p){t(34,B=String(p).includes("clipboard is empty")?v("inbox.clipboardEmpty"):v("inbox.clipboardUnavailable"))}finally{t(66,Ve=!1)}}}async function Mm(p,O="drop"){if(!Ve){t(66,Ve=!0),t(67,nt="");try{for(const se of p){const _e=await ae("CapturePathWithContext",se,O,al());await Nl(_e)}t(67,nt=v("inbox.captured"))}catch(se){t(34,B=String(se))}finally{t(66,Ve=!1),t(106,tl=!1)}}}async function xo(p){if(!(d||m)&&!js(p.target)&&p.clipboardData)try{await Ei(p.clipboardData,"paste")&&(p.preventDefault(),t(67,nt=v("inbox.captured")))}catch(O){t(34,B=String(O))}}function $o(p){if(In.length>0)return!1;const O=Array.from((p==null?void 0:p.types)||[]);return O.includes("Files")||O.includes("text/uri-list")||O.includes("text/x-moz-url")||O.includes("text/plain")&&!O.includes("application/x-verstak-node")}function er(){q&&(clearTimeout(q),q=null)}function Lt(){Ln=0,t(107,Mn=!1),t(108,jn=""),Ee=0,t(106,tl=!1),er()}function tr(){q||(q=setTimeout(()=>{q=null,Mn&&Date.now()-Ee>2e3?Lt():Mn&&tr()},2500))}function nr(p){return $o(p)?(t(108,jn=Am()),t(107,Mn=!0),Ee=Date.now(),tr(),!0):!1}function lr(p){nr(p.dataTransfer)&&(Ln+=1)}function sr(p){nr(p.dataTransfer)&&(p.preventDefault(),p.dataTransfer.dropEffect="copy")}function ir(p){Ln>0&&(Ln-=1),(p.clientX<=0||p.clientY<=0||p.clientX>=window.innerWidth||p.clientY>=window.innerHeight||Ln<=0)&&Lt()}function or(){document.hidden&&Lt()}async function rr(p){if($o(p.dataTransfer)){p.preventDefault();try{await Ei(p.dataTransfer,"drop")&&t(67,nt=v("inbox.captured"))}catch(O){t(34,B=String(O))}finally{Lt()}}}function Lm(p){p.preventDefault(),p.dataTransfer.dropEffect="copy",t(106,tl=!0)}function Om(){t(106,tl=!1)}async function Dm(p){p.preventDefault(),p.stopPropagation();try{await Ei(p.dataTransfer,"drop")&&t(67,nt=v("inbox.captured"))}catch(O){t(34,B=String(O))}finally{Lt()}}function Cs(p){t(117,On=p),t(118,Un=""),t(119,nl=[]),t(120,Hn=null)}function Ti(){t(117,On=null),t(118,Un=""),t(119,nl=[]),t(120,Hn=null),t(121,gs=!1)}async function Pm(){const p=Un.trim();if(!p||p.length<2){t(119,nl=[]);return}t(121,gs=!0);try{const O=await ae("SearchNodes",p)||[];t(119,nl=O.filter(se=>P5(se)&&se.id!==(On==null?void 0:On.id)))}catch{t(119,nl=[])}finally{t(121,gs=!1)}}function zm(p){t(118,Un=p.target.value),t(120,Hn=null),clearTimeout(mo),mo=setTimeout(Pm,200)}function ar(p){t(120,Hn=p),t(118,Un=p.path||p.title),t(119,nl=[])}async function cr(){if(!(!On||!Hn))try{await ur(On,Hn.id),Ti()}catch(p){t(34,B=String(p))}}async function ur(p,O){!p||!O||(await ae("ResolveInboxNode",p.id,O),t(3,tt=tt.filter(se=>se.id!==p.id)),t(65,Ze=Ze.filter(se=>se.id!==p.id)),await il(),X&&await vs(X.id))}async function As(p){const O=(p==null?void 0:p.suggestedTargetNodeId)||(X==null?void 0:X.id)||"";if(O)try{await ur(p,O)}catch(se){t(34,B=String(se))}}function Ns(p){dn({title:v("inbox.deleteTitle"),message:v("inbox.deleteConfirm",{title:p.title}),confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("DeleteInboxNode",p.id),t(3,tt=tt.filter(O=>O.id!==p.id)),t(65,Ze=Ze.filter(O=>O.id!==p.id))}catch(O){t(34,B=String(O))}}})}function fr(p){t(69,jt=p),t(70,qt=p.title||""),t(71,at=p.url||""),t(72,Ut=p.note||""),t(73,hn="")}function Si(){t(69,jt=null),t(70,qt=""),t(71,at=""),t(72,Ut=""),t(73,hn="")}async function Bm(){if(!(!jt||!at.trim()))try{const p=await ae("UpdateLink",jt.id,qt.trim(),at.trim(),Ut);t(68,Dt=Dt.map(O=>O.id===p.id?p:O)),Si()}catch(p){t(73,hn=String(p))}}async function dr(p){dn({title:v("links.deleteTitle"),message:v("links.deleteConfirm",{title:p.title}),confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("DeleteLink",p.id),t(68,Dt=Dt.filter(O=>O.id!==p.id))}catch(O){t(34,B=String(O))}}})}async function pr(p){try{await ae("OpenLink",p.id)}catch(O){t(34,B=String(O))}}async function mr(p){var O,se;try{await((se=(O=navigator.clipboard)==null?void 0:O.writeText)==null?void 0:se.call(O,p.url)),t(73,hn=v("links.copied"))}catch{t(73,hn=v("links.copyUnavailable"))}}function Fm(){t(86,Jn=!0),t(87,bn=""),t(88,Qn="open_url"),t(89,vn="")}function gr(){t(86,Jn=!1),t(87,bn=""),t(89,vn="")}async function Ci(){if(!(!bn.trim()||!vn.trim()||!X))try{const p=await ae("CreateAction",X.id,Qn,bn.trim(),vn.trim());p&&p.id&&t(48,be=[...be,p]),t(86,Jn=!1),t(87,bn=""),t(89,vn="")}catch(p){t(34,B=String(p))}}async function hr(p){try{await ae("DeleteAction",p),t(48,be=be.filter(O=>O.id!==p))}catch(O){t(34,B=String(O))}}function jm(p){const O=cn.find(se=>se.id===p);return O?O.label:p}async function Xl(p){try{const O=await ae("GetNodeDetail",p);O&&Pt(O)}catch(O){t(34,B=String(O))}}async function cl(p){const O=z5(p),se=O.nodeId;if(se)try{const _e=await ae("GetNodeDetail",se);if(!_e)return;if(Pt(_e),O.tab==="notes"){if(Qt("notes"),O.targetId){try{t(36,te=await ae("ListNotes",se)||[])}catch{}const De=te.find(wt=>wt.id===O.targetId);De&&setTimeout(()=>wn(De),100)}}else if(O.tab==="files")if(Qt("files"),O.targetId)try{const De=await ae("GetNodeDetail",O.targetId);if(De&&De.parent_id){await yt(De.parent_id);const wt=gt.find(Il=>Il.id===O.targetId);wt&&wt.type==="file"&&Ii(wt)&&setTimeout(()=>ll(wt),150)}else await yt(se)}catch{await yt(se)}else await yt(se)}catch(_e){t(34,B=String(_e))}}async function Rl(p){return cl(p)}async function Um(p){if(!(!p||!p.nodeId))try{if(p.type==="link"){const se=await ae("GetNodeDetail",p.nodeId);se&&(await Pt(se),Qt("links"));return}if(p.type==="action"){const se=await ae("GetNodeDetail",p.nodeId);se&&(await Pt(se),Qt("actions"));return}const O=await ae("GetNodeDetail",p.nodeId);if(!O)return;if(p.type==="note"){const se=O.parent_id||O.parentId||"",_e=se?await ae("GetNodeDetail",se):null;if(_e){await Pt(_e),Qt("notes"),t(36,te=await ae("ListNotes",_e.id)||[]);const De=te.find(wt=>wt.id===O.id);De&&await wn(De)}return}if(p.type==="file"){const se=O.parent_id||O.parentId||"",_e=se?await ae("GetNodeDetail",se):null;if(_e){await Pt(_e),Qt("files"),await yt(_e.id);const De=gt.find(wt=>wt.id===O.id);De&&Ii(De)&&await ll(De)}return}if(p.type==="folder"){await Pt(O),Qt("files"),await yt(O.id);return}await Pt(O)}catch(O){t(34,B=String(O))}}async function Ai(){try{t(122,ei=await ae("SyncStatus"))}catch{t(122,ei={configured:!1,serverUrl:"",deviceId:"",unpushedOps:0,lastSyncAt:"",syncInterval:0})}}let _r="general";function Ni(p){t(126,_r=p||"general"),t(13,g=!0)}function br(){t(13,g=!1)}async function Hm(){try{t(0,h=await ae("ListSystemViewsWithPlugins")||[])}catch{}}function Wm(p){const O=Array.isArray(p==null?void 0:p.conflicts)?p.conflicts:[],se=Array.isArray(p==null?void 0:p.applyErrors)?p.applyErrors:[],_e=[];return O.length>0&&_e.push(v("sync.conflictsCount",{count:O.length})),se.length>0&&_e.push(v("sync.applyErrorsCount",{count:se.length})),_e.join(" · ")}async function Gm(){t(123,ti=!0),t(124,Zl=""),t(125,hs="");try{const p=await ae("SyncNow");await Ai(),t(124,Zl=Wm(p)),t(125,hs=Zl?"warning":"")}catch(p){console.error("sync error:",p),t(124,Zl=`${v("sync.status.error")}: ${(p==null?void 0:p.message)||p}`),t(125,hs="warning")}finally{t(123,ti=!1)}}function Km(p){t(11,d=!1),t(10,c=p),t(90,Nn=!1),window.location.reload()}function Vm(p){t(12,m=!1),t(10,c=p),t(90,Nn=!1),window.location.reload()}function qm(p){Qi.call(this,n,p)}const Zm=p=>p.nodeId===X.id,Ym=p=>bs(p.id),Xm=()=>Ni("sync"),Jm=()=>Ni(),Qm=()=>hi(Mt),xm=()=>_i(Mt),$m=()=>ae("OpenTrashFolder"),eg=()=>t(34,B=""),tg=()=>t(34,B=""),ng=()=>t(34,B="");function lg(){ge=this.value,t(47,ge)}const sg=p=>{p.key==="Enter"&&fi(),p.key==="Escape"&&Gn()},ig=()=>ui(ne.id,ne.title);function og(p){zn[p?"unshift":"push"](()=>{de=p,t(38,de)})}const rg=p=>Qt(p.id),ag=()=>{Qt("notes"),Po()},cg=()=>{Qt("files"),Zo()},ug=()=>Qt("worklog"),fg=p=>wn(p),dg=p=>wn(p);function pg(){Jt=this.value,t(85,Jt)}const mg=p=>p.key==="Enter"&&Bo(),gg=p=>ui(p.id,p.title),hg=p=>jo(p),_g=p=>wn(p),bg=p=>wn(p),vg=p=>{const O=p.detail;O===0?(t(97,vt=[]),t(96,bt=null),yt(X.id)):_o(O-1)},kg=p=>si(p.detail),wg=p=>ll(p.detail),yg=p=>ae("OpenFile",p.detail),Eg=p=>ae("OpenFolder",p.detail),Tg=p=>Jo(p.detail),Sg=p=>vo(p.detail.id),Cg=p=>bo(p.detail),Ag=p=>ko(p.detail),Ng=p=>wo(p.detail),Rg=p=>To(p.detail),Ig=p=>Eo(p.detail),Mg=p=>So(p.detail),Lg=p=>As(p),Og=p=>Cs(p),Dg=p=>pn(p),Pg=p=>Dn(p),zg=p=>Ns(p),Bg=p=>pn(p),Fg=(p,O)=>O.key==="Enter"&&pn(p),jg=p=>pr(p),Ug=p=>mr(p),Hg=p=>fr(p),Wg=p=>dr(p),Gg=p=>ae("RunAction",p.id),Kg=p=>hr(p.id),Vg=()=>ws(),qg=p=>p.nodeId===X.id,Zg=p=>Es(p),Yg=p=>bi(p),Xg=p=>Ts(p),Jg=(p,O,se)=>t(59,O[se]._expanded=!p._expanded,pe,t(2,X)),Qg=(p,O,se,_e)=>_e.key==="Enter"&&t(59,O[se]._expanded=!p._expanded,pe,t(2,X)),xg=p=>cl(p),$g=p=>Dn(p.nodeId),e1=p=>ws(p),t1=p=>pi(p),n1=p=>cl(p),l1=p=>Dn(p.nodeId),s1=p=>ki(p),i1=(p,O)=>O.key==="Enter"&&ki(p),o1=p=>Rl(p),r1=(p,O)=>O.key==="Enter"&&Rl(p),a1=()=>{t(4,fe="date"),t(5,Me=fe==="date"&&Me==="desc"?"asc":"desc")},c1=()=>{t(4,fe="name"),t(5,Me=fe==="name"&&Me==="desc"?"asc":"desc")},u1=()=>{t(4,fe="type"),t(5,Me=fe==="type"&&Me==="desc"?"asc":"desc")};function f1(){Ot=this.checked,t(6,Ot)}const d1=p=>As(p),p1=p=>Cs(p),m1=p=>pn(p),g1=p=>Dn(p),h1=p=>Ns(p),_1=p=>pn(p),b1=(p,O)=>O.key==="Enter"&&pn(p),v1=p=>As(p),k1=p=>Cs(p),w1=p=>pn(p),y1=p=>Dn(p),E1=p=>Ns(p),T1=p=>pn(p),S1=(p,O)=>O.key==="Enter"&&pn(p),C1=p=>Wo(p.id),A1=p=>p.type!=="file"?Al(p):Yl(p),N1=(p,O)=>O.key==="Enter"&&(p.type!=="file"?Al(p):Yl(p)),R1=p=>p.type!=="file"?Al(p):Yl(p),I1=(p,O)=>O.key==="Enter"&&(p.type!=="file"?Al(p):Yl(p)),M1=p=>hi(gi(p.id)),L1=p=>_i(gi(p.id)),O1=()=>t(28,K="suggestions"),D1=()=>t(28,K="worklog"),P1=p=>Xl(p.nodeId),z1=(p,O,se,_e)=>t(59,O[se].suggestedMin=parseInt(_e.target.value),pe),B1=p=>Es(p),F1=p=>Go(p),j1=p=>Ts(p),U1=(p,O,se)=>t(59,O[se]._expanded=!p._expanded,pe),H1=(p,O,se,_e)=>_e.key==="Enter"&&t(59,O[se]._expanded=!p._expanded,pe),W1=p=>cl(p),G1=p=>Dn(p.nodeId),K1=()=>Ss("csv"),V1=()=>Ss("markdown"),q1=()=>Ss("pdf");function Z1(){I=this.value,t(20,I)}function Y1(){M=this.value,t(21,M)}const X1=()=>{t(29,V=""),t(26,W=""),qo()};function J1(){V=this.value,t(29,V)}const Q1=()=>setTimeout(()=>t(31,G=!1),200),x1=p=>Vo(p);function $1(){R=this.checked,t(22,R)}function eh(){z=Bs(this),t(24,z)}function th(){U=Bs(this),t(25,U)}const nh=p=>Xl(p.nodeId),lh=p=>vi(p),sh=(p,O)=>O.key==="Enter"&&vi(p),ih=p=>ws(p),oh=p=>pi(p),rh=p=>cl(p),ah=p=>Xl(p),ch=p=>Rl(p),uh=p=>cl(p),fh=p=>Es(p),dh=p=>bi(p),ph=p=>Ts(p),mh=p=>Dn(p),gh=p=>pn(p),hh=p=>{bs("trash"),Al({id:p,title:""}),ol()},_h=p=>Rl(p),bh=(p,O)=>O.key==="Enter"&&Rl(p),vh=()=>t(82,en=null),kh=p=>t(82,en=p);function wh(){Gt=this.value,t(80,Gt)}const yh=p=>p.key==="Enter"&&Mo(),Eh=p=>Ro(p),Th=()=>Do(tn.node),Sh=()=>Lo(tn.node),Ch=()=>Oo(tn.node),Ah=()=>Dn(tn.node);function Nh(){Ue=this.value,t(53,Ue)}function Rh(){Ce=this.value,t(51,Ce)}function Ih(){je=Hl(this.value),t(52,je)}function Mh(){Pe=this.value,t(54,Pe)}function Lh(){Fe=this.checked,t(55,Fe)}function Oh(){ue=this.checked,t(56,ue)}function Dh(){bn=this.value,t(87,bn)}const Ph=p=>p.key==="Enter"&&Ci();function zh(){Qn=Bs(this),t(88,Qn),t(131,cn)}function Bh(){vn=this.value,t(89,vn)}const Fh=p=>p.key==="Enter"&&Ci(),jh=()=>wi("copy"),Uh=()=>wi("link");function Hh(){kn=this.value,t(115,kn)}function Wh(){Un=this.value,t(118,Un)}const Gh=p=>p.key==="Enter"&&Hn&&cr(),Kh=p=>ar(p);function Vh(){qt=this.value,t(70,qt)}function qh(){at=this.value,t(71,at)}function Zh(){Ut=this.value,t(72,Ut)}const Yh=p=>ae("OpenFile",p.detail);function Xh(){qe=this.value,t(41,qe)}const Jh=p=>{p.key==="Enter"&&ci(),p.key==="Escape"&&t(40,ze=!1)};function Qh(){Ne=this.value,t(42,Ne)}const xh=p=>{p.key==="Enter"&&ci(),p.key==="Escape"&&t(40,ze=!1)},$h=()=>t(40,ze=!1),e_=()=>t(40,ze=!1),t_=p=>p.key==="Escape"&&t(40,ze=!1);function n_(){ge=this.value,t(47,ge)}const l_=p=>{p.key==="Enter"&&fi(),p.key==="Escape"&&Gn()},s_=p=>p.key==="Escape"&&Gn();return n.$$.update=()=>{var p;n.$$.dirty[0]&384&&t(76,Tl=I5((Ht==null?void 0:Ht.nodes)||[],Wt)),n.$$.dirty[0]&7&&t(129,l=X?X.title:Z?((p=h.find(O=>O.id===Z))==null?void 0:p.label)||"":v("nav.selectPrompt")),n.$$.dirty[0]&4&&t(128,s=X?yi(X.type):""),n.$$.dirty[0]&56&&t(9,o=A5(tt,fe,Me)),n.$$.dirty[0]&576&&t(127,i=Ot?yp(o):null),n.$$.dirty[8]&65536&&t(77,ms=_t.length===0?v("nav.trash"):[v("nav.trash"),..._t.map(O=>O.title)].join(" / "))},t(78,Fn=((vr=am())==null?void 0:vr.title)||v("trash.deletedNodes")),[h,Z,X,tt,fe,Me,Ot,Ht,Wt,o,c,d,m,g,_,b,k,E,N,A,I,M,R,L,z,U,W,F,K,V,Y,G,D,j,B,$,te,ne,de,Ae,ze,qe,Ne,ee,re,Se,Be,ge,be,it,Oe,Ce,je,Ue,Pe,Fe,ue,ye,x,pe,me,Re,ot,Je,rt,Ze,Ve,nt,Dt,jt,qt,at,Ut,hn,El,Mt,Tl,ms,Fn,ct,Gt,An,en,tn,_n,Jt,Jn,bn,Qn,vn,Nn,fn,Sl,xn,Zt,$n,bt,vt,gt,Rt,nn,el,Rn,ln,$e,ql,tl,Mn,jn,le,ke,Le,Ge,xe,It,kn,At,On,Un,nl,Hn,gs,ei,ti,Zl,hs,_r,i,s,l,J,cn,wp,Qt,bs,Pt,yt,si,Sp,_o,ll,ks,Cp,bo,vo,ko,wo,yo,Eo,To,So,Lp,Op,Dp,No,Bp,Fp,jp,Up,Ro,Hp,Io,Mo,Wp,sl,Gp,Kp,Vp,qp,Zp,Lo,Oo,Dn,pn,Do,Po,zo,Bo,wn,jo,Uo,Yp,ci,Qp,xp,ui,Gn,fi,$p,lm,sm,im,om,ws,Ho,rm,pi,ol,Al,cm,Yl,Wo,gi,hi,_i,um,Es,Ts,fm,dm,pm,mm,bi,Go,rl,Ss,vi,ki,hm,Vo,qo,_m,Zo,bm,wi,Xo,Jo,km,yi,wm,Tm,Im,Lm,Om,Dm,Cs,Ti,zm,ar,cr,As,Ns,fr,Si,Bm,dr,pr,mr,Fm,gr,Ci,hr,jm,Xl,cl,Rl,Um,Ai,Ni,br,Hm,Gm,Km,Vm,_t,qm,Zm,Ym,Xm,Jm,Qm,xm,$m,eg,tg,ng,lg,sg,ig,og,rg,ag,cg,ug,fg,dg,pg,mg,gg,hg,_g,bg,vg,kg,wg,yg,Eg,Tg,Sg,Cg,Ag,Ng,Rg,Ig,Mg,Lg,Og,Dg,Pg,zg,Bg,Fg,jg,Ug,Hg,Wg,Gg,Kg,Vg,qg,Zg,Yg,Xg,Jg,Qg,xg,$g,e1,t1,n1,l1,s1,i1,o1,r1,a1,c1,u1,f1,d1,p1,m1,g1,h1,_1,b1,v1,k1,w1,y1,E1,T1,S1,C1,A1,N1,R1,I1,M1,L1,O1,D1,P1,z1,B1,F1,j1,U1,H1,W1,G1,K1,V1,q1,Z1,Y1,X1,J1,Q1,x1,$1,eh,th,nh,lh,sh,ih,oh,rh,ah,ch,uh,fh,dh,ph,mh,gh,hh,_h,bh,vh,kh,wh,yh,Eh,Th,Sh,Ch,Ah,Nh,Rh,Ih,Mh,Lh,Oh,Dh,Ph,zh,Bh,Fh,jh,Uh,Hh,Wh,Gh,Kh,Vh,qh,Zh,Yh,Xh,Jh,Qh,xh,$h,e_,t_,n_,l_,s_]}class F5 extends pt{constructor(e){super(),dt(this,e,B5,C5,ft,{},null,[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1])}}new F5({target:document.getElementById("app")}); +`,ne),t(37,ne.dirty=!0,ne),t(40,ze=!1)}function Jp(){t(43,ee=!0)}function Qp(p){const O=p.detail.markdown;de&&typeof de.insertText=="function"&&de.insertText(O),t(43,ee=!1)}function xp(){t(43,ee=!1)}function ui(p,O){t(46,Be=p),t(47,ge=O),We=O}function Gn(){t(46,Be=null),t(47,ge=""),We=""}async function fi(){if(!Be||!ge.trim()){Gn();return}if(ge.trim()===We){Gn();return}try{await ae("RenameNote",Be,ge.trim()),t(36,te=te.map(p=>p.id===Be?{...p,title:ge.trim()}:p)),ne&&ne.id===Be&&t(37,ne.title=ge.trim(),ne),Gn()}catch(p){t(34,B=String(p)),Gn()}}async function $p(p){const{type:O,id:se}=p.detail;if(se)switch(O){case"case":await em(se);break;case"note":await tm(se);break;case"file":await nm(se);break;case"secret":yn(v("note.internal.secretNotImplemented"));break;default:yn(`verstak://${O}/${se}`)}}async function em(p){try{const O=await ae("GetNodeDetail",p);if(!O){yn(v("note.internal.caseNotFound"));return}await Pt(O)}catch{yn(v("note.internal.caseNotFound"))}}async function tm(p){try{const O=await ae("GetNodeDetail",p);if(!O){yn(v("note.internal.noteNotFound"));return}const se=O.parent_id||O.parentId||"";if(se){const De=await ae("GetNodeDetail",se);De&&await Pt(De)}Qt("notes");const _e=te.find(De=>De.id===p);_e?await wn(_e):await wn({id:O.id,title:O.title})}catch{yn(v("note.internal.noteNotFound"))}}async function nm(p){try{const O=await ae("GetNodeDetail",p);if(!O){yn(v("note.internal.fileNotFound"));return}const se=O.parent_id||O.parentId||"";if(se){const _e=await ae("GetNodeDetail",se);_e&&await Pt(_e),Qt("files"),await yt(se);const De=gt.find(wt=>wt.id===p);De?await ll(De):yn(v("note.internal.fileFound",{title:O.title}))}else yn(v("note.internal.fileFound",{title:O.title}))}catch{yn(v("note.internal.fileNotFound"))}}function yn(p){t(45,Se=p),t(44,re=!0),setTimeout(()=>{t(44,re=!1)},3e3)}function lm(p){ne&&(t(37,ne.content=p.detail.content,ne),t(37,ne.dirty=!0,ne))}function sm(p){t(39,Ae=p.detail.mode)}function im(){Xp()}function om(){Jp()}function ws(p=null){t(57,ye=p),t(58,x=null),t(51,Ce=p?p.summary:""),t(52,je=p?String(p.minutes||""):""),t(53,Ue=p&&p.date||""),t(54,Pe=p&&p.details||""),t(55,Fe=p?!!p.billable:!1),t(56,ue=p?!!p.approximate:!1),t(50,Oe=!0)}function Ho(){t(50,Oe=!1),t(57,ye=null),t(58,x=null)}async function di(p=""){const O=p||(X?X.id:"");X&&X.id===O&&t(49,it=ji(await ae("ListWorklog",X.id))||it),Z==="journal"&&await rl()}async function rm(){const p=parseInt(je,10);if(!(!Ce.trim()||isNaN(p)||p<=0)&&!(!x&&!ye&&!X)){try{if(x){const O=JSON.stringify(Ds(x));await ae("AcceptSuggestionFull",x.nodeId,Ce.trim(),Pe,Ue,p,ue,Fe,O),await ys()}else ye?(await ae("UpdateWorklogEntry",ye.id,Ce.trim(),Pe,Ue,p,ue,Fe),await di(ye.nodeId)):(await ae("CreateWorklogFull",X.id,Ce.trim(),Pe,Ue,p,ue,Fe),await di(X.id))}catch(O){t(34,B=String(O));return}t(50,Oe=!1),t(57,ye=null),t(58,x=null)}}function pi(p){dn({title:v("worklog.deleteEntry"),message:v("worklog.deleteConfirm"),confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("DeleteWorklogEntry",p.id),await di(p.nodeId)}catch(O){t(34,B=String(O))}}})}async function ys(){t(59,pe=await ae("GetSuggestions")||[]),t(60,me=pe.length),X&&t(49,it=ji(await ae("ListWorklog",X.id))),Z==="journal"&&await rl()}async function ol(){t(7,Ht=await ae("ListTrash")||{nodes:[],entries:[],trashPath:"",count:0}),t(74,El=Ht.count||0);const p=Ht.nodes||[];t(75,Mt=Mt.filter(O=>p.some(se=>se.id===O))),Wt&&!p.some(O=>O.id===Wt)?li():_t.length>0&&(t(264,_t=_t.filter(O=>p.some(se=>se.id===O.id))),t(8,Wt=_t.length>0?_t[_t.length-1].id:""))}async function mi(){try{t(74,El=await ae("TrashCount")||0)}catch{t(74,El=0)}}function am(){return Wt&&(((Ht==null?void 0:Ht.nodes)||[]).find(p=>p.id===Wt)||_t[_t.length-1])||null}function Al(p){Wn(),t(8,Wt=p.id);const O=_t.findIndex(se=>se.id===p.id);O>=0?t(264,_t=_t.slice(0,O+1)):t(264,_t=[..._t,{id:p.id,title:p.title}]),t(75,Mt=[])}function cm(){if(_t.length===0)return;Wn();const p=_t.slice(0,-1);t(264,_t=p),t(8,Wt=p.length>0?p[p.length-1].id:""),t(75,Mt=[])}async function Yl(p){t(99,Rt={name:p.title,type:"file",mime:"text/plain",size:0,fileId:p.id}),t(100,nn=""),t(102,Rn=""),t(101,el=!0);try{p.trashFsPath?t(100,nn=await ae("ReadTrashFile",p.trashFsPath)||""):t(100,nn=await ae("ReadTrashFileContent",p.id)||"");const O=(p.title||"").split(".").pop().toLowerCase();["png","jpg","jpeg","gif","webp","bmp","svg"].includes(O)&&t(100,nn="data:image/"+(O==="svg"?"svg+xml":O)+";base64,"+btoa(nn))}catch(O){t(102,Rn=String(O))}t(101,el=!1)}function Wo(p){t(75,Mt=Mt.includes(p)?Mt.filter(O=>O!==p):[...Mt,p])}function gi(p){return Mt.length>0?Mt:[p]}async function hi(p){try{await ae("RestoreTrashNodesJSON",JSON.stringify(p)),await il(),await ol()}catch(O){t(34,B=String(O))}}async function _i(p){dn({title:v("delete.confirmTitle"),message:v("delete.confirmMessage")+" "+p.length+"?",confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("PurgeTrashNodesJSON",JSON.stringify(p)),await ol()}catch(O){t(34,B=String(O))}}})}async function um(){dn({title:v("delete.confirmTitle"),message:v("trash.empty")+"?",confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("EmptyTrash"),await ol()}catch(p){t(34,B=String(p))}}})}function Es(p){t(58,x=p),t(57,ye=null),t(51,Ce=p.summary||""),t(52,je=String(p.suggestedMin||"")),t(53,Ue=""),t(54,Pe=""),t(55,Fe=!1),t(56,ue=!0),t(50,Oe=!0)}async function Ts(p){try{await ae("DismissSuggestion",p.nodeId,JSON.stringify(Ds(p))),await ys()}catch(O){t(34,B=String(O))}}async function fm(){t(64,rt=!0);try{const p=await ae("ListBrowserEvents","pending",50,0);t(63,Je=p||[])}catch(p){console.warn("[browser] load error:",p)}finally{t(64,rt=!1)}}async function dm(p){await ae("AcceptBrowserEvent",p.id,""),t(63,Je=Je.filter(O=>O.id!==p.id))}async function pm(p){await ae("DismissBrowserEvent",p.id),t(63,Je=Je.filter(O=>O.id!==p.id))}async function mm(p){X&&X.id?(await ae("AttachBrowserEventToNode",p.id,X.id),t(63,Je=Je.filter(O=>O.id!==p.id))):(await ae("DismissBrowserEvent",p.id),t(63,Je=Je.filter(O=>O.id!==p.id)))}async function bi(p){try{const O=Ds(p),se=JSON.stringify(O);Ps("acceptTodaySuggestion: nodeId="+p.nodeId+" eventIds="+se+" events="+JSON.stringify((p.events||[]).map(_e=>({id:_e.id,type:_e.eventType,title:_e.title})))),await ae("AcceptSuggestionWith",p.nodeId,p.summary,p.suggestedMin,"",se),await ys()}catch(O){Ps("acceptTodaySuggestion error: "+O)}}async function Go(p){try{const O=Ds(p),se=JSON.stringify(O);Ps("acceptJournalSuggestion: nodeId="+p.nodeId+" eventIds="+se+" events="+JSON.stringify((p.events||[]).map(_e=>({id:_e.id,type:_e.eventType,title:_e.title})))),await ae("AcceptSuggestionWith",p.nodeId,p.summary,p.suggestedMin,"",se),await ys()}catch(O){Ps("acceptJournalSuggestion error: "+O)}}async function rl(){try{const[p,O,se]=await Promise.all([ae("ListWorklogReport",I,M,L,R,z,U),ae("WorklogReportSummary",I,M,L,R,z,U),ae("GetSuggestions")]);if(t(18,N=(p||[]).map(_e=>(_e._expanded===void 0&&(_e._expanded=!1),_e))),t(19,A=O||null),t(59,pe=se||[]),t(60,me=pe.length),L&&!W)if(p&&p.length>0&&p[0].nodePath)t(26,W=p[0].nodePath);else try{t(26,W=await ae("GetNodeTitle",L))}catch{t(26,W="")}}catch{t(18,N=[]),t(19,A=null),t(59,pe=[]),t(60,me=0)}}async function Ss(p){try{const O=await ae("SaveWorklogReport",p,I,M,L,R,z,U);t(27,F=O),setTimeout(()=>t(27,F=""),4e3)}catch(O){if(String(O).includes("отменено"))return;t(27,F="Ошибка: "+String(O)),setTimeout(()=>t(27,F=""),6e3)}}let Ko;async function vi(p){if(p._expanded=!p._expanded,t(18,N),p._expanded&&!p._events&&p._hasEvents===void 0)try{p._events=await ae("GetWorklogEntryEvents",p.id)||[],p._hasEvents=p._events.length>0,t(18,N)}catch{p._events=[],p._hasEvents=!1,t(18,N)}}async function ki(p){if(p._expanded=!p._expanded,p._expanded&&p.source==="suggestion"&&!p._events&&p._hasEvents===void 0)try{p._events=await ae("GetWorklogEntryEvents",p.id)||[],p._hasEvents=p._events.length>0}catch(O){O._events=[],O._hasEvents=!1}t(49,it)}async function gm(){const p=V.trim();if(!p||p.length<2){t(30,Y=[]),t(31,G=!1);return}try{t(30,Y=await ae("SearchNodes",p)||[]),t(31,G=Y.length>0)}catch{t(30,Y=[]),t(31,G=!1)}}function hm(){clearTimeout(Ko),Ko=setTimeout(gm,200)}function Vo(p){t(23,L=p.id),t(26,W=p.path||p.title),t(22,R=!0),t(29,V=""),t(30,Y=[]),t(31,G=!1),rl()}function qo(){t(23,L=""),t(22,R=!1),t(26,W=""),t(29,V=""),t(30,Y=[]),t(31,G=!1),rl()}function _m(){t(20,I=""),t(21,M=""),t(23,L=""),t(22,R=!1),t(24,z="all"),t(25,U="all"),t(26,W=""),t(29,V=""),t(30,Y=[]),t(31,G=!1),rl()}async function Zo(){const p=await ae("PickFile");if(!p)return;const O=bt||X.id;await Yo(O,p)}async function bm(){const p=await ae("PickDirectory");if(!p)return;const O=bt||X.id;await Yo(O,p)}async function Yo(p,O){t(91,fn=!0);try{const se=await ae("PreviewImport",O);t(92,Sl=se),Cl=O,Vl=p,t(93,xn=!0)}catch(se){t(34,B=String(se))}t(91,fn=!1)}async function wi(p){try{const O=Vl||X.id,se=p==="copy"?await ae("AddPathCopy",O,Cl):await ae("AddPathLink",O,Cl);t(93,xn=!1),t(92,Sl=null),t(97,vt=[]),t(96,bt=null),await Promise.all([vs(O),yt(O),ai(O)])}catch(O){t(34,B=String(O))}}function Xo(){t(93,xn=!1),t(92,Sl=null)}async function Jo({id:p,type:O}){const se=v(O==="folder"?"delete.folder":"delete.file");dn({title:v("delete.confirmTitle"),message:v("delete.confirmMessage")+" "+se+"?",confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("DeleteFileOrFolder",p),mt=mt.filter(De=>De.nodeId!==p);const _e=bt||X.id;await yt(_e)}catch(_e){t(34,B=String(_e))}}})}async function vm(p){try{if(!p||p.length===0)return;await Mm(p,"drop")}finally{Lt()}}function km(p){return{note_created:v("event.noteCreated"),note_updated:v("event.noteUpdated"),note_deleted:v("event.noteDeleted"),file_added:v("event.fileAdded"),file_deleted:v("event.fileDeleted"),file_renamed:v("event.fileRenamed"),file_copied:v("event.fileCopied"),file_moved:v("event.fileMoved"),folder_added:v("event.folderAdded"),folder_deleted:v("event.folderDeleted"),folder_renamed:v("event.folderRenamed"),folder_moved:v("event.folderMoved"),node_created:v("event.caseCreated"),node_updated:v("event.caseUpdated"),node_deleted:v("event.nodeDeleted"),action_created:v("event.actionCreated"),action_done:v("event.actionDone"),worklog_added:v("event.worklogAdded")}[p]||p}function yi(p){return{project:v("kind.project"),client:v("kind.client"),document:v("kind.document"),recipe:v("kind.recipe"),folder:v("kind.folder"),note:v("kind.note"),file:v("kind.file"),archive:v("kind.archive"),case:v("kind.case"),link:v("kind.link")}[p]||p||v("kind.case")}function wm(p){return p==="action"?v("kind.action"):yi(p)}function ym(p){return p?v("capture.kind."+p):""}function Em(p){return p?v("capture.source."+p):""}function Tm(p){const O=[];return p.captureKind&&O.push(ym(p.captureKind)),p.captureSource&&O.push(Em(p.captureSource)),p.captureContextLabel&&O.push(`${v("inbox.capturedIn")}: ${p.captureContextLabel}`),p.suggestedTargetLabel&&O.push(`${v("inbox.suggestedTarget")}: ${p.suggestedTargetLabel}`),O.push(an(p.capturedAt||p.createdAt)),O.filter(Boolean).join(" · ")}function Sm(p){!p||!p.id||(t(3,tt=[p,...tt.filter(O=>O.id!==p.id)]),X&&(p.captureContextNodeId===X.id||p.suggestedTargetNodeId===X.id)&&t(65,Ze=[p,...Ze.filter(O=>O.id!==p.id)]))}function Cm(){return X&&X.id?{contextType:"node",nodeId:X.id,suggestedTargetNodeId:X.id}:Z?{contextType:"section",section:Z}:{contextType:"global",section:"root"}}function al(){return JSON.stringify(Cm())}function Am(){return X&&X.id?v("capture.dropOverlayNode",{title:X.title}):v("capture.dropOverlayGlobal")}async function Nl(p=null){p&&Sm(p),Z==="inbox"&&t(3,tt=await ae("ListInboxNodes")||tt)}async function Nm(p,O){const se=String(p||"").trim();if(!se)return null;const _e=Ui(se),De=_e?await ae("CaptureURLWithContext",_e,"",O,al()):await ae("CaptureTextWithContext",se,O,al());return await Nl(De),De}async function Qo(p,O,se){const _e=String(p||"").trim();if(!_e)return null;const De=await ae("CaptureURLWithContext",_e,O||"",se,al());return await Nl(De),De}async function Rm(p,O){if(!p)return null;const se=p.path||p.webkitRelativePath||"";if(se){const wt=await ae("CapturePathWithContext",se,O,al());return await Nl(wt),wt}const _e=await L5(p),De=await ae("CaptureFileDataWithContext",p.name||`clipboard.${M5(p.type)}`,_e,O,al());return await Nl(De),De}async function Ei(p,O){var Jl,En,kr;if(!p)return!1;let se=!1;const _e=Array.from(p.files||[]);for(const ul of _e)await Rm(ul,O),se=!0;const De=(Jl=p.getData)==null?void 0:Jl.call(p,"text/x-moz-url");if(De){const ul=O5(De),Ql=ul?Ui(ul.url):"";if(Ql)return await Qo(Ql,ul.title,O),!0}const wt=(En=p.getData)==null?void 0:En.call(p,"text/uri-list");if(wt){const ul=D5(wt),Ql=Ui(ul);if(Ql)return await Qo(Ql,"",O),!0}const Il=(kr=p.getData)==null?void 0:kr.call(p,"text/plain");return String(Il||"").trim()&&(await Nm(Il,O),se=!0),se}async function Im(){if(!Ve){t(67,nt=""),t(66,Ve=!0);try{const p=await ae("CaptureClipboardTextWithContext",al());await Nl(p),t(67,nt=v("inbox.captured"))}catch(p){t(34,B=String(p).includes("clipboard is empty")?v("inbox.clipboardEmpty"):v("inbox.clipboardUnavailable"))}finally{t(66,Ve=!1)}}}async function Mm(p,O="drop"){if(!Ve){t(66,Ve=!0),t(67,nt="");try{for(const se of p){const _e=await ae("CapturePathWithContext",se,O,al());await Nl(_e)}t(67,nt=v("inbox.captured"))}catch(se){t(34,B=String(se))}finally{t(66,Ve=!1),t(106,tl=!1)}}}async function xo(p){if(!(d||m)&&!js(p.target)&&p.clipboardData)try{await Ei(p.clipboardData,"paste")&&(p.preventDefault(),t(67,nt=v("inbox.captured")))}catch(O){t(34,B=String(O))}}function $o(p){if(In.length>0)return!1;const O=Array.from((p==null?void 0:p.types)||[]);return O.includes("Files")||O.includes("text/uri-list")||O.includes("text/x-moz-url")||O.includes("text/plain")&&!O.includes("application/x-verstak-node")}function er(){q&&(clearTimeout(q),q=null)}function Lt(){Ln=0,t(107,Mn=!1),t(108,jn=""),Ee=0,t(106,tl=!1),er()}function tr(){q||(q=setTimeout(()=>{q=null,Mn&&Date.now()-Ee>2e3?Lt():Mn&&tr()},2500))}function nr(p){return $o(p)?(t(108,jn=Am()),t(107,Mn=!0),Ee=Date.now(),tr(),!0):!1}function lr(p){nr(p.dataTransfer)&&(Ln+=1)}function sr(p){nr(p.dataTransfer)&&(p.preventDefault(),p.dataTransfer.dropEffect="copy")}function ir(p){Ln>0&&(Ln-=1),(p.clientX<=0||p.clientY<=0||p.clientX>=window.innerWidth||p.clientY>=window.innerHeight||Ln<=0)&&Lt()}function or(){document.hidden&&Lt()}async function rr(p){if($o(p.dataTransfer)){p.preventDefault();try{await Ei(p.dataTransfer,"drop")&&t(67,nt=v("inbox.captured"))}catch(O){t(34,B=String(O))}finally{Lt()}}}function Lm(p){p.preventDefault(),p.dataTransfer.dropEffect="copy",t(106,tl=!0)}function Om(){t(106,tl=!1)}async function Dm(p){p.preventDefault(),p.stopPropagation();try{await Ei(p.dataTransfer,"drop")&&t(67,nt=v("inbox.captured"))}catch(O){t(34,B=String(O))}finally{Lt()}}function Cs(p){t(117,On=p),t(118,Un=""),t(119,nl=[]),t(120,Hn=null)}function Ti(){t(117,On=null),t(118,Un=""),t(119,nl=[]),t(120,Hn=null),t(121,gs=!1)}async function Pm(){const p=Un.trim();if(!p||p.length<2){t(119,nl=[]);return}t(121,gs=!0);try{const O=await ae("SearchNodes",p)||[];t(119,nl=O.filter(se=>P5(se)&&se.id!==(On==null?void 0:On.id)))}catch{t(119,nl=[])}finally{t(121,gs=!1)}}function zm(p){t(118,Un=p.target.value),t(120,Hn=null),clearTimeout(mo),mo=setTimeout(Pm,200)}function ar(p){t(120,Hn=p),t(118,Un=p.path||p.title),t(119,nl=[])}async function cr(){if(!(!On||!Hn))try{await ur(On,Hn.id),Ti()}catch(p){t(34,B=String(p))}}async function ur(p,O){!p||!O||(await ae("ResolveInboxNode",p.id,O),t(3,tt=tt.filter(se=>se.id!==p.id)),t(65,Ze=Ze.filter(se=>se.id!==p.id)),await il(),X&&await vs(X.id))}async function As(p){const O=(p==null?void 0:p.suggestedTargetNodeId)||(X==null?void 0:X.id)||"";if(O)try{await ur(p,O)}catch(se){t(34,B=String(se))}}function Ns(p){dn({title:v("inbox.deleteTitle"),message:v("inbox.deleteConfirm",{title:p.title}),confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("DeleteInboxNode",p.id),t(3,tt=tt.filter(O=>O.id!==p.id)),t(65,Ze=Ze.filter(O=>O.id!==p.id))}catch(O){t(34,B=String(O))}}})}function fr(p){t(69,jt=p),t(70,qt=p.title||""),t(71,at=p.url||""),t(72,Ut=p.note||""),t(73,hn="")}function Si(){t(69,jt=null),t(70,qt=""),t(71,at=""),t(72,Ut=""),t(73,hn="")}async function Bm(){if(!(!jt||!at.trim()))try{const p=await ae("UpdateLink",jt.id,qt.trim(),at.trim(),Ut);t(68,Dt=Dt.map(O=>O.id===p.id?p:O)),Si()}catch(p){t(73,hn=String(p))}}async function dr(p){dn({title:v("links.deleteTitle"),message:v("links.deleteConfirm",{title:p.title}),confirmText:v("common.delete"),danger:!0,onConfirm:async()=>{try{await ae("DeleteLink",p.id),t(68,Dt=Dt.filter(O=>O.id!==p.id))}catch(O){t(34,B=String(O))}}})}async function pr(p){try{await ae("OpenLink",p.id)}catch(O){t(34,B=String(O))}}async function mr(p){var O,se;try{await((se=(O=navigator.clipboard)==null?void 0:O.writeText)==null?void 0:se.call(O,p.url)),t(73,hn=v("links.copied"))}catch{t(73,hn=v("links.copyUnavailable"))}}function Fm(){t(86,Jn=!0),t(87,bn=""),t(88,Qn="open_url"),t(89,vn="")}function gr(){t(86,Jn=!1),t(87,bn=""),t(89,vn="")}async function Ci(){if(!(!bn.trim()||!vn.trim()||!X))try{const p=await ae("CreateAction",X.id,Qn,bn.trim(),vn.trim());p&&p.id&&t(48,be=[...be,p]),t(86,Jn=!1),t(87,bn=""),t(89,vn="")}catch(p){t(34,B=String(p))}}async function hr(p){try{await ae("DeleteAction",p),t(48,be=be.filter(O=>O.id!==p))}catch(O){t(34,B=String(O))}}function jm(p){const O=cn.find(se=>se.id===p);return O?O.label:p}async function Xl(p){try{const O=await ae("GetNodeDetail",p);O&&Pt(O)}catch(O){t(34,B=String(O))}}async function cl(p){const O=z5(p),se=O.nodeId;if(se)try{const _e=await ae("GetNodeDetail",se);if(!_e)return;if(Pt(_e),O.tab==="notes"){if(Qt("notes"),O.targetId){try{t(36,te=await ae("ListNotes",se)||[])}catch{}const De=te.find(wt=>wt.id===O.targetId);De&&setTimeout(()=>wn(De),100)}}else if(O.tab==="files")if(Qt("files"),O.targetId)try{const De=await ae("GetNodeDetail",O.targetId);if(De&&De.parent_id){await yt(De.parent_id);const wt=gt.find(Il=>Il.id===O.targetId);wt&&wt.type==="file"&&Ii(wt)&&setTimeout(()=>ll(wt),150)}else await yt(se)}catch{await yt(se)}else await yt(se)}catch(_e){t(34,B=String(_e))}}async function Rl(p){return cl(p)}async function Um(p){if(!(!p||!p.nodeId))try{if(p.type==="link"){const se=await ae("GetNodeDetail",p.nodeId);se&&(await Pt(se),Qt("links"));return}if(p.type==="action"){const se=await ae("GetNodeDetail",p.nodeId);se&&(await Pt(se),Qt("actions"));return}const O=await ae("GetNodeDetail",p.nodeId);if(!O)return;if(p.type==="note"){const se=O.parent_id||O.parentId||"",_e=se?await ae("GetNodeDetail",se):null;if(_e){await Pt(_e),Qt("notes"),t(36,te=await ae("ListNotes",_e.id)||[]);const De=te.find(wt=>wt.id===O.id);De&&await wn(De)}return}if(p.type==="file"){const se=O.parent_id||O.parentId||"",_e=se?await ae("GetNodeDetail",se):null;if(_e){await Pt(_e),Qt("files"),await yt(_e.id);const De=gt.find(wt=>wt.id===O.id);De&&Ii(De)&&await ll(De)}return}if(p.type==="folder"){await Pt(O),Qt("files"),await yt(O.id);return}await Pt(O)}catch(O){t(34,B=String(O))}}async function Ai(){try{t(122,ei=await ae("SyncStatus"))}catch{t(122,ei={configured:!1,serverUrl:"",deviceId:"",unpushedOps:0,lastSyncAt:"",syncInterval:0})}}let _r="general";function Ni(p){t(126,_r=p||"general"),t(13,g=!0)}function br(){t(13,g=!1)}async function Hm(){try{t(0,h=await ae("ListSystemViewsWithPlugins")||[])}catch{}}function Wm(p){const O=Array.isArray(p==null?void 0:p.conflicts)?p.conflicts:[],se=Array.isArray(p==null?void 0:p.applyErrors)?p.applyErrors:[],_e=[];return O.length>0&&_e.push(v("sync.conflictsCount",{count:O.length})),se.length>0&&_e.push(v("sync.applyErrorsCount",{count:se.length})),_e.join(" · ")}async function Gm(){t(123,ti=!0),t(124,Zl=""),t(125,hs="");try{const p=await ae("SyncNow");await Ai(),t(124,Zl=Wm(p)),t(125,hs=Zl?"warning":"")}catch(p){console.error("sync error:",p),t(124,Zl=`${v("sync.status.error")}: ${(p==null?void 0:p.message)||p}`),t(125,hs="warning")}finally{t(123,ti=!1)}}function Km(p){t(11,d=!1),t(10,c=p),t(90,Nn=!1),window.location.reload()}function Vm(p){t(12,m=!1),t(10,c=p),t(90,Nn=!1),window.location.reload()}function qm(p){Qi.call(this,n,p)}const Zm=p=>p.nodeId===X.id,Ym=p=>bs(p.id),Xm=()=>Ni("sync"),Jm=()=>Ni(),Qm=()=>hi(Mt),xm=()=>_i(Mt),$m=()=>ae("OpenTrashFolder"),eg=()=>t(34,B=""),tg=()=>t(34,B=""),ng=()=>t(34,B="");function lg(){ge=this.value,t(47,ge)}const sg=p=>{p.key==="Enter"&&fi(),p.key==="Escape"&&Gn()},ig=()=>ui(ne.id,ne.title);function og(p){zn[p?"unshift":"push"](()=>{de=p,t(38,de)})}const rg=p=>Qt(p.id),ag=()=>{Qt("notes"),Po()},cg=()=>{Qt("files"),Zo()},ug=()=>Qt("worklog"),fg=p=>wn(p),dg=p=>wn(p);function pg(){Jt=this.value,t(85,Jt)}const mg=p=>p.key==="Enter"&&Bo(),gg=p=>ui(p.id,p.title),hg=p=>jo(p),_g=p=>wn(p),bg=p=>wn(p),vg=p=>{const O=p.detail;O===0?(t(97,vt=[]),t(96,bt=null),yt(X.id)):_o(O-1)},kg=p=>si(p.detail),wg=p=>ll(p.detail),yg=p=>ae("OpenFile",p.detail),Eg=p=>ae("OpenFolder",p.detail),Tg=p=>Jo(p.detail),Sg=p=>vo(p.detail.id),Cg=p=>bo(p.detail),Ag=p=>ko(p.detail),Ng=p=>wo(p.detail),Rg=p=>To(p.detail),Ig=p=>Eo(p.detail),Mg=p=>So(p.detail),Lg=p=>As(p),Og=p=>Cs(p),Dg=p=>pn(p),Pg=p=>Dn(p),zg=p=>Ns(p),Bg=p=>pn(p),Fg=(p,O)=>O.key==="Enter"&&pn(p),jg=p=>pr(p),Ug=p=>mr(p),Hg=p=>fr(p),Wg=p=>dr(p),Gg=p=>ae("RunAction",p.id),Kg=p=>hr(p.id),Vg=()=>ws(),qg=p=>p.nodeId===X.id,Zg=p=>Es(p),Yg=p=>bi(p),Xg=p=>Ts(p),Jg=(p,O,se)=>t(59,O[se]._expanded=!p._expanded,pe,t(2,X)),Qg=(p,O,se,_e)=>_e.key==="Enter"&&t(59,O[se]._expanded=!p._expanded,pe,t(2,X)),xg=p=>cl(p),$g=p=>Dn(p.nodeId),e1=p=>ws(p),t1=p=>pi(p),n1=p=>cl(p),l1=p=>Dn(p.nodeId),s1=p=>ki(p),i1=(p,O)=>O.key==="Enter"&&ki(p),o1=p=>Rl(p),r1=(p,O)=>O.key==="Enter"&&Rl(p),a1=()=>{t(4,fe="date"),t(5,Me=fe==="date"&&Me==="desc"?"asc":"desc")},c1=()=>{t(4,fe="name"),t(5,Me=fe==="name"&&Me==="desc"?"asc":"desc")},u1=()=>{t(4,fe="type"),t(5,Me=fe==="type"&&Me==="desc"?"asc":"desc")};function f1(){Ot=this.checked,t(6,Ot)}const d1=p=>As(p),p1=p=>Cs(p),m1=p=>pn(p),g1=p=>Dn(p),h1=p=>Ns(p),_1=p=>pn(p),b1=(p,O)=>O.key==="Enter"&&pn(p),v1=p=>As(p),k1=p=>Cs(p),w1=p=>pn(p),y1=p=>Dn(p),E1=p=>Ns(p),T1=p=>pn(p),S1=(p,O)=>O.key==="Enter"&&pn(p),C1=p=>Wo(p.id),A1=p=>p.type!=="file"?Al(p):Yl(p),N1=(p,O)=>O.key==="Enter"&&(p.type!=="file"?Al(p):Yl(p)),R1=p=>p.type!=="file"?Al(p):Yl(p),I1=(p,O)=>O.key==="Enter"&&(p.type!=="file"?Al(p):Yl(p)),M1=p=>hi(gi(p.id)),L1=p=>_i(gi(p.id)),O1=()=>t(28,K="suggestions"),D1=()=>t(28,K="worklog"),P1=p=>Xl(p.nodeId),z1=(p,O,se,_e)=>t(59,O[se].suggestedMin=parseInt(_e.target.value),pe),B1=p=>Es(p),F1=p=>Go(p),j1=p=>Ts(p),U1=(p,O,se)=>t(59,O[se]._expanded=!p._expanded,pe),H1=(p,O,se,_e)=>_e.key==="Enter"&&t(59,O[se]._expanded=!p._expanded,pe),W1=p=>cl(p),G1=p=>Dn(p.nodeId),K1=()=>Ss("csv"),V1=()=>Ss("markdown"),q1=()=>Ss("pdf");function Z1(){I=this.value,t(20,I)}function Y1(){M=this.value,t(21,M)}const X1=()=>{t(29,V=""),t(26,W=""),qo()};function J1(){V=this.value,t(29,V)}const Q1=()=>setTimeout(()=>t(31,G=!1),200),x1=p=>Vo(p);function $1(){R=this.checked,t(22,R)}function eh(){z=Bs(this),t(24,z)}function th(){U=Bs(this),t(25,U)}const nh=p=>Xl(p.nodeId),lh=p=>vi(p),sh=(p,O)=>O.key==="Enter"&&vi(p),ih=p=>ws(p),oh=p=>pi(p),rh=p=>cl(p),ah=p=>Xl(p),ch=p=>Rl(p),uh=p=>cl(p),fh=p=>Es(p),dh=p=>bi(p),ph=p=>Ts(p),mh=p=>Dn(p),gh=p=>pn(p),hh=p=>{bs("trash"),Al({id:p,title:""}),ol()},_h=p=>Rl(p),bh=(p,O)=>O.key==="Enter"&&Rl(p),vh=()=>t(82,en=null),kh=p=>t(82,en=p);function wh(){Gt=this.value,t(80,Gt)}const yh=p=>p.key==="Enter"&&Mo(),Eh=p=>Ro(p),Th=()=>Do(tn.node),Sh=()=>Lo(tn.node),Ch=()=>Oo(tn.node),Ah=()=>Dn(tn.node);function Nh(){Ue=this.value,t(53,Ue)}function Rh(){Ce=this.value,t(51,Ce)}function Ih(){je=Hl(this.value),t(52,je)}function Mh(){Pe=this.value,t(54,Pe)}function Lh(){Fe=this.checked,t(55,Fe)}function Oh(){ue=this.checked,t(56,ue)}function Dh(){bn=this.value,t(87,bn)}const Ph=p=>p.key==="Enter"&&Ci();function zh(){Qn=Bs(this),t(88,Qn),t(131,cn)}function Bh(){vn=this.value,t(89,vn)}const Fh=p=>p.key==="Enter"&&Ci(),jh=()=>wi("copy"),Uh=()=>wi("link");function Hh(){kn=this.value,t(115,kn)}function Wh(){Un=this.value,t(118,Un)}const Gh=p=>p.key==="Enter"&&Hn&&cr(),Kh=p=>ar(p);function Vh(){qt=this.value,t(70,qt)}function qh(){at=this.value,t(71,at)}function Zh(){Ut=this.value,t(72,Ut)}const Yh=p=>ae("OpenFile",p.detail);function Xh(){qe=this.value,t(41,qe)}const Jh=p=>{p.key==="Enter"&&ci(),p.key==="Escape"&&t(40,ze=!1)};function Qh(){Ne=this.value,t(42,Ne)}const xh=p=>{p.key==="Enter"&&ci(),p.key==="Escape"&&t(40,ze=!1)},$h=()=>t(40,ze=!1),e_=()=>t(40,ze=!1),t_=p=>p.key==="Escape"&&t(40,ze=!1);function n_(){ge=this.value,t(47,ge)}const l_=p=>{p.key==="Enter"&&fi(),p.key==="Escape"&&Gn()},s_=p=>p.key==="Escape"&&Gn();return n.$$.update=()=>{var p;n.$$.dirty[0]&384&&t(76,Tl=I5((Ht==null?void 0:Ht.nodes)||[],Wt)),n.$$.dirty[0]&7&&t(129,l=X?X.title:Z?((p=h.find(O=>O.id===Z))==null?void 0:p.label)||"":v("nav.selectPrompt")),n.$$.dirty[0]&4&&t(128,s=X?yi(X.type):""),n.$$.dirty[0]&56&&t(9,o=A5(tt,fe,Me)),n.$$.dirty[0]&576&&t(127,i=Ot?yp(o):null),n.$$.dirty[8]&65536&&t(77,ms=_t.length===0?v("nav.trash"):[v("nav.trash"),..._t.map(O=>O.title)].join(" / "))},t(78,Fn=((vr=am())==null?void 0:vr.title)||v("trash.deletedNodes")),[h,Z,X,tt,fe,Me,Ot,Ht,Wt,o,c,d,m,g,_,b,k,E,N,A,I,M,R,L,z,U,W,F,K,V,Y,G,D,j,B,$,te,ne,de,Ae,ze,qe,Ne,ee,re,Se,Be,ge,be,it,Oe,Ce,je,Ue,Pe,Fe,ue,ye,x,pe,me,Re,ot,Je,rt,Ze,Ve,nt,Dt,jt,qt,at,Ut,hn,El,Mt,Tl,ms,Fn,ct,Gt,An,en,tn,_n,Jt,Jn,bn,Qn,vn,Nn,fn,Sl,xn,Zt,$n,bt,vt,gt,Rt,nn,el,Rn,ln,$e,ql,tl,Mn,jn,le,ke,Le,Ge,xe,It,kn,At,On,Un,nl,Hn,gs,ei,ti,Zl,hs,_r,i,s,l,J,cn,wp,Qt,bs,Pt,yt,si,Sp,_o,ll,ks,Cp,bo,vo,ko,wo,yo,Eo,To,So,Lp,Op,Dp,No,Bp,Fp,jp,Up,Ro,Hp,Io,Mo,Wp,sl,Gp,Kp,Vp,qp,Zp,Lo,Oo,Dn,pn,Do,Po,zo,Bo,wn,jo,Uo,Yp,ci,Qp,xp,ui,Gn,fi,$p,lm,sm,im,om,ws,Ho,rm,pi,ol,Al,cm,Yl,Wo,gi,hi,_i,um,Es,Ts,fm,dm,pm,mm,bi,Go,rl,Ss,vi,ki,hm,Vo,qo,_m,Zo,bm,wi,Xo,Jo,km,yi,wm,Tm,Im,Lm,Om,Dm,Cs,Ti,zm,ar,cr,As,Ns,fr,Si,Bm,dr,pr,mr,Fm,gr,Ci,hr,jm,Xl,cl,Rl,Um,Ai,Ni,br,Hm,Gm,Km,Vm,_t,qm,Zm,Ym,Xm,Jm,Qm,xm,$m,eg,tg,ng,lg,sg,ig,og,rg,ag,cg,ug,fg,dg,pg,mg,gg,hg,_g,bg,vg,kg,wg,yg,Eg,Tg,Sg,Cg,Ag,Ng,Rg,Ig,Mg,Lg,Og,Dg,Pg,zg,Bg,Fg,jg,Ug,Hg,Wg,Gg,Kg,Vg,qg,Zg,Yg,Xg,Jg,Qg,xg,$g,e1,t1,n1,l1,s1,i1,o1,r1,a1,c1,u1,f1,d1,p1,m1,g1,h1,_1,b1,v1,k1,w1,y1,E1,T1,S1,C1,A1,N1,R1,I1,M1,L1,O1,D1,P1,z1,B1,F1,j1,U1,H1,W1,G1,K1,V1,q1,Z1,Y1,X1,J1,Q1,x1,$1,eh,th,nh,lh,sh,ih,oh,rh,ah,ch,uh,fh,dh,ph,mh,gh,hh,_h,bh,vh,kh,wh,yh,Eh,Th,Sh,Ch,Ah,Nh,Rh,Ih,Mh,Lh,Oh,Dh,Ph,zh,Bh,Fh,jh,Uh,Hh,Wh,Gh,Kh,Vh,qh,Zh,Yh,Xh,Jh,Qh,xh,$h,e_,t_,n_,l_,s_]}class F5 extends pt{constructor(e){super(),dt(this,e,B5,C5,ft,{},null,[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1])}}window.addEventListener("error",n=>{const e=n.error?String(n.error):n.message,t=n.error&&n.error.stack?n.error.stack:"";console.error("[Frontend Error]",e,t);try{window.go&&window.go.main&&window.go.main.App&&typeof window.go.main.App.FrontendLog=="function"&&window.go.main.App.FrontendLog("error",e,t)}catch{}});window.addEventListener("unhandledrejection",n=>{const e=n.reason,t=e instanceof Error?String(e):JSON.stringify(e),l=e instanceof Error&&e.stack?e.stack:"";console.error("[Unhandled Promise Rejection]",t,l);try{window.go&&window.go.main&&window.go.main.App&&typeof window.go.main.App.FrontendLog=="function"&&window.go.main.App.FrontendLog("error","Unhandled rejection: "+t,l)}catch{}});try{new F5({target:document.getElementById("app")})}catch(n){console.error("[Fatal] App mount failed:",n);const e=document.getElementById("app");e&&(e.innerHTML='

Frontend Runtime Error

'+String(n)+"

"+(n.stack?"
"+n.stack+"
":"")+"
")} diff --git a/cmd/verstak-gui/frontend-dist/index.html b/cmd/verstak-gui/frontend-dist/index.html index 43d5664..ebff653 100644 --- a/cmd/verstak-gui/frontend-dist/index.html +++ b/cmd/verstak-gui/frontend-dist/index.html @@ -19,7 +19,7 @@ background: #13131f; } - + diff --git a/docs/frontend-architecture.md b/docs/frontend-architecture.md new file mode 100644 index 0000000..a7785bc --- /dev/null +++ b/docs/frontend-architecture.md @@ -0,0 +1,178 @@ +# Frontend Architecture — Verstak GUI + +## Tech Stack + +- **Framework**: Svelte 4 (runes-free, compiler-only) +- **Build tool**: Vite 5 +- **GUI shell**: Wails v2 (Go backend + WebKit frontend) +- **Language**: Plain JavaScript (no TypeScript in Svelte files — `lang="ts"` is NOT used) + +## Source Structure + +``` +frontend/src/ + main.js # Entry: mounts App.svelte, global error handlers + App.svelte # Root component: sidebar, header, modals, tab router + FileTreeRow.svelte # Sidebar tree node row + TreeNode.svelte # Recursive tree node + lib/ + AppHeader.svelte # Top bar with title, search, actions + BrowserEvents.svelte # Browser extension events panel + CalendarPluginPage.svelte # Plugin page host + ConfirmModal.svelte # Reusable confirm dialog + FileBreadcrumbs.svelte # File tab breadcrumb navigation + FileIcon.svelte # Icon resolver for file types + FilePreviewModal.svelte # File content preview modal + FirstRun.svelte # First-run wizard + GlobalSearch.svelte # Global search bar + results + SettingsWindow.svelte # Settings modal container + Settings*.svelte # Settings sub-sections (General, Sync, etc.) + SyncStatus.svelte # Sync status badge + TemplateIcon.svelte # Template type icon + TodayScreen.svelte # Today dashboard + VaultRecovery.svelte # Vault recovery wizard + actionIcons.js # SVG icon strings + fileUtils.js # File type helpers (canPreview, isMarkdown, etc.) + i18n/ # Internationalization (en, ru) + markdown/ # Markdown rendering + internal links + util/ # Keyboard layout helper + components/ + notes/ + MarkdownEditor.svelte # Markdown textarea with toolbar + MarkdownPreview.svelte # Rendered markdown preview + NoteEditorPanel.svelte # Editor + preview layout, public API: insertText() + InternalLinkPicker.svelte # Object picker for verstak:// links + ObjectPickerModal.svelte # Legacy object picker modal +``` + +## Role of App.svelte + +`App.svelte` is the **root component**. It owns: + +1. **Global UI state**: sidebar (system views, workspace tree), active tab, selected node/section +2. **Lifecycle**: startup checks (GetStartupStatus, VerstakVersion, ListWorkspaceTree), event listeners +3. **Top-level modals**: confirm, rename, import, worklog, inbox assign, link edit, settings, file preview +4. **Cross-cutting concerns**: navigation history (goBack, rememberNavigation), keyboard shortcuts, drag-and-drop orchestration, capture/inbox flow + +App.svelte is **NOT** responsible for: +- Individual tab content (Overview, Notes, Files) — these are inlined but should be extracted +- Note editor internals — `NoteEditorPanel.svelte` + `MarkdownEditor.svelte` handle this +- Settings sections — each has its own `Settings*.svelte` + +## State Ownership Rules + +### App.svelte MUST own: +- `selectedSection`, `selectedNode`, `activeTab` +- `systemViews`, `workspaceTree`, `enabledTemplates` +- `noteEditor` (the note being edited), `noteViewMode` +- `showFirstRun`, `showRecovery`, `showSettings`, `loading` +- `startupStatus`, `startupChecked` +- Navigation state: `navHistory`, `restoringHistory` +- Trash browser state: `trashInfo`, `trashCount`, `trashSelectedIds`, `trashFolderId`, `trashFolderStack` +- Journal state: `journalRows`, `journalSummary`, filters +- Worklog modal state: `showWorklogModal`, `wlModal*` +- Inbox: `inboxNodes`, `localInboxNodes`, capture state +- Links: `links` +- Sync: `syncStatus` +- `error` (global error banner) + +### App.svelte must NOT directly own (after extraction): +- Files tab internal state → `FilesTab.svelte` +- Notes tab list UI state → `NotesTab.svelte` +- Overview tab content → `OverviewTab.svelte` + +## Component Communication + +### Props (parent → child) +Data flows down via Svelte `export let prop` + +### Events (child → parent) +Children dispatch events via `createEventDispatcher()` +- Suffix `:` in event names means handler in template: `on:openNote={handler}`, NOT call on mount +- All events should use `e.detail` to pass data + +### Public API (bind:this) +Parent gets imperative handle via `bind:this={ref}` and calls: +- `ref.publicMethod(args)` — guard with optional chaining: `ref?.publicMethod?.(args)` +- Methods are exposed via `export function` in child component + +## Services (Wails API) + +All backend calls go through the `wailsCall()` helper in App.svelte: + +```js +function wailsCall(method, ...args) { + // Returns Promise, rejects with 'Wails not connected: ' if backend unavailable +} +``` + +Key backend methods: +- **Startup**: `GetStartupStatus`, `VerstakVersion`, `ListSystemViewsWithPlugins`, `ListWorkspaceTree`, `ListEnabledTemplates` +- **Nodes**: `GetNodeDetail`, `ListItems`, `ListWorkspaceChildren`, `CreateNodeFromTemplate`, `DeleteNode`, `MoveNode`, `RenameNode` +- **Notes**: `ListNotes`, `CreateNote`, `ReadNote`, `SaveNote`, `DeleteNote`, `RenameNote` +- **Files**: `ListFiles`, `DeleteFileOrFolder`, `CreateEmptyFile`, `DuplicateNode`, `OpenFile`, `OpenFolder`, `GetFileBase64`, `ReadFileText`, `PreviewImport`, `AddPathCopy`, `AddPathLink` +- **Worklog**: `ListWorklog`, `CreateWorklogFull`, `UpdateWorklogEntry`, `DeleteWorklogEntry`, `AcceptSuggestionFull/With`, `GetSuggestions` +- **Inbox**: `ListInboxNodes`, `Capture*WithContext`, `DeleteInboxNode`, `ResolveInboxNode` +- **Sync**: `SyncStatus`, `SyncNow`, `TrashCount`, `ListTrash` +- **Logging**: `FrontendLog(level, message, stack)`, `LogStartupStep(step, success, detail)` + +## Logging & Diagnostics + +### Frontend errors +- Global `window.addEventListener('error', ...)` catches uncaught exceptions +- Global `window.addEventListener('unhandledrejection', ...)` catches broken promises +- Both log to `console.error` and forward to `FrontendLog()` backend binding +- Fatal mount error: renders error message inline in `#app` div (blank window becomes diagnostic) + +### Backend logs +- Application log file: `~/.local/state/verstak/logs/verstak.log` (Linux) +- Fallback: `~/.config/verstak/logs/verstak.log` +- Format: `[timestamp] [level] message` +- Go `log.Printf()` output goes to stderr (visible in dev console) + +### Vault debug log +- `/.verstak/debug.log` — written by `WriteDebugLog` binding (existing feature) + +## Refactor Rules + +### After each extraction step: +1. `grep` App.svelte for state that moved to child — must be zero hits or documented exceptions +2. `npm run build` — must pass +3. `go test ./...` — must pass +4. Manual GUI check: + - Window is not blank/white + - Sidebar shows (system views + workspace tree) + - No infinite "Загрузка..." on welcome screen + - Clicking system sections works + - Opening a case shows tabs + +### Forbidden patterns: +- Child component state referenced directly in App.svelte after extraction +- `lang="ts"` in Svelte files (svelte-preprocess not installed) +- `[]string` passed directly to Wails bindings (must JSON-serialize) +- Modal without `role="dialog" aria-modal="true"` and Escape handler +- Buttons without `type="button"` in forms +- `resize: none` on textareas that should be resizable (use `resize: vertical`) + +## Troubleshooting + +### Blank window, no errors in terminal +1. Check browser DevTools console (F12 on Windows/Linux, Cmd+Alt+I on macOS) +2. In Wails dev mode: right-click → Inspect → Console +3. Look for `[Frontend Error]` messages +4. Check application log: `~/.local/state/verstak/logs/verstak.log` + +### Stuck on "Загрузка..." +- `GetStartupStatus` likely failed or returned unexpected status +- Check network calls in DevTools Network tab +- Check backend log for startup errors + +### Wails method not found +- Ensure method is registered in `bindings*.go` files +- Ensure method is exported (capitalized name) +- Ensure `wailsCall()` helper is used (not direct window access) + +### Child component state moved but App still references old variable +- This causes a silent `undefined` reference or runtime crash +- Run `grep -n 'oldStateName' App.svelte` after each extraction +- Use `ref?.method?.()` for all public API calls on child components diff --git a/frontend/src/main.js b/frontend/src/main.js index a750cc1..3b3bc4a 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,5 +1,48 @@ import App from './App.svelte' -new App({ - target: document.getElementById('app') +// ===== Global frontend error diagnostics ===== +// These catch runtime errors that would otherwise cause a silent blank window. + +window.addEventListener('error', (event) => { + const msg = event.error ? String(event.error) : event.message + const stack = event.error && event.error.stack ? event.error.stack : '' + console.error('[Frontend Error]', msg, stack) + try { + if (window['go'] && window['go']['main'] && window['go']['main']['App'] && typeof window['go']['main']['App']['FrontendLog'] === 'function') { + window['go']['main']['App']['FrontendLog']('error', msg, stack) + } + } catch (e) { + // Backend not ready — ignore + } }) + +window.addEventListener('unhandledrejection', (event) => { + const reason = event.reason + const msg = reason instanceof Error ? String(reason) : JSON.stringify(reason) + const stack = reason instanceof Error && reason.stack ? reason.stack : '' + console.error('[Unhandled Promise Rejection]', msg, stack) + try { + if (window['go'] && window['go']['main'] && window['go']['main']['App'] && typeof window['go']['main']['App']['FrontendLog'] === 'function') { + window['go']['main']['App']['FrontendLog']('error', 'Unhandled rejection: ' + msg, stack) + } + } catch (e) { + // Backend not ready — ignore + } +}) + +// Mount App with error fallback +try { + new App({ + target: document.getElementById('app') + }) +} catch (e) { + console.error('[Fatal] App mount failed:', e) + const el = document.getElementById('app') + if (el) { + el.innerHTML = '
' + + '

Frontend Runtime Error

' + + '

' + String(e) + '

' + + (e.stack ? '
' + e.stack + '
' : '') + + '
' + } +} diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index 559630b..0c659f3 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -243,5 +243,25 @@ export function CountActivityByNode(arg1) { } export function CreateEmptyFile(arg1, arg2) { - return window['go']['main']['App']['CreateEmptyFile'](arg1, arg2); + return window['go']['main']['App']['CreateEmptyFile'](arg1, arg2) +} + +// ===== Logging & Diagnostics ===== + +export function FrontendLog(level, message, stack) { + try { + if (window['go'] && window['go']['main'] && window['go']['main']['App']) { + return window['go']['main']['App']['FrontendLog'](level, message, stack || '') + } + } catch (e) {} + return Promise.resolve() +} + +export function LogStartupStep(step, success, detail) { + try { + if (window['go'] && window['go']['main'] && window['go']['main']['App']) { + return window['go']['main']['App']['LogStartupStep'](step, success, detail || '') + } + } catch (e) {} + return Promise.resolve() }