-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
486 lines (448 loc) · 25.3 KB
/
Copy pathmain.go
File metadata and controls
486 lines (448 loc) · 25.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
// Package main embeds the built frontend, sets up the window/tray, and
// constructs + wires every bounded-context service. No domain logic
// lives here (.claude/rules/backend.md).
package main
import (
"embed"
"log"
"log/slog"
"os"
"path/filepath"
"time"
"github.com/alicoding/mill/internal/adapters/credential"
"github.com/alicoding/mill/internal/adapters/launchatlogin"
"github.com/alicoding/mill/internal/adapters/settings"
"github.com/alicoding/mill/internal/adapters/windowing"
"github.com/alicoding/mill/internal/services/agentloopsvc"
"github.com/alicoding/mill/internal/services/atlassvc"
"github.com/alicoding/mill/internal/services/backupsvc"
"github.com/alicoding/mill/internal/services/capabilitysvc"
"github.com/alicoding/mill/internal/services/clipboardhistorysvc"
"github.com/alicoding/mill/internal/services/codeloopsvc"
"github.com/alicoding/mill/internal/services/companionsvc"
"github.com/alicoding/mill/internal/services/compositionsvc"
"github.com/alicoding/mill/internal/services/configuresvc"
"github.com/alicoding/mill/internal/services/dataevent"
"github.com/alicoding/mill/internal/services/docssvc"
"github.com/alicoding/mill/internal/services/executionsvc"
"github.com/alicoding/mill/internal/services/guardrailsvc"
"github.com/alicoding/mill/internal/services/mcpsvc"
"github.com/alicoding/mill/internal/services/notificationsvc"
"github.com/alicoding/mill/internal/services/settingssvc"
"github.com/alicoding/mill/internal/services/triggersvc"
"github.com/alicoding/mill/internal/services/wiring"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
// millChannel/millVersion/millUpdateVersion are the build-stamp trio
// release.yml (channel=release) and ci.yml's beta job (channel=beta)
// override via ldflags -X; every other build keeps the defaults below.
// millChannel gates settingssvc's install-and-restart path. millVersion
// feeds run receipts/the backup manifest/the release updater's
// CurrentVersion, and must agree with the git tag and build/config.yml
// (release.yml's verify step enforces this) -- a beta build never
// touches it. millUpdateVersion is what the updater compares the beta
// feed's rolling tag against and AppVersion shows in Settings: a SemVer
// prerelease compares strictly below its release, so a beta based on
// the already-shipped millVersion (e.g. "0.5.0-beta.N" once v0.5.0
// exists) would rank below that release forever -- ci.yml's beta job
// stamps a per-build id based on the NEXT release instead.
var millChannel = "source"
const millVersion = "0.5.0"
var millUpdateVersion = millVersion
// Wails uses Go's `embed` package to embed the frontend files into the binary.
// Any files in the frontend/dist folder will be embedded into the binary and
// made available to the frontend.
// See https://pkg.go.dev/embed for more information.
//go:embed all:frontend/dist
var assets embed.FS
// The in-app Docs surface's content (goal 0125 phase 1): the same
// userdocs tree the repository publishes and llms.txt indexes,
// embedded so docs ship inside the binary like the frontend does.
//
//go:embed all:userdocs
var userdocsFS embed.FS
//go:embed build/appicon.png
var trayIconPNG []byte
func init() {
// Each RegisterEvent[T] gives the binding generator a typed JS/TS API.
application.RegisterEvent[string]("time")
application.RegisterEvent[triggersvc.HotkeyActivity]("hotkey-activity")
application.RegisterEvent[mcpsvc.MCPWriteRequest]("mcp-write-approval")
application.RegisterEvent[mcpsvc.MCPWriteActivity]("mcp-write-activity")
application.RegisterEvent[dataevent.Changed](dataevent.EventName)
application.RegisterEvent[atlassvc.MirrorChanged](atlassvc.MirrorChangedEvent)
application.RegisterEvent[executionsvc.GuardrailPendingChanged]("guardrail-pending-changed")
application.RegisterEvent[companionsvc.CompanionDelta](companionsvc.DeltaEventName)
application.RegisterEvent[agentloopsvc.AgentLoopEvent](agentloopsvc.StateEventName)
application.RegisterEvent[agentloopsvc.AgentLoopDelta](agentloopsvc.DeltaEventName)
// docs/adr/0033: OpenMainWindow emits this so App.tsx can switch views
// once the main window is back in front -- broadcast to every window.
application.RegisterEvent[string]("mill-navigate")
}
// main initializes the application, creates the window, and wires every
// bounded-context service together.
func main() {
// Create a new Wails application by providing the necessary options.
// Variables 'Name' and 'Description' are for application metadata.
// 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files.
// 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances.
// 'Mac' options tailor the application when running an macOS.
// Reuses Wails3's own default logger (colorized to stderr in dev mode via isatty detection, silently discarded in
// production builds — see application.DefaultLogger's per-build-tag implementations) instead of wiring up a second,
// parallel slog handler. Passed to both Mill's own services and application.Options.Logger so app-level events (a
// hotkey firing) and Wails3's own system messages share one stream.
logger := application.DefaultLogger(slog.LevelInfo)
// application.Path resolves the OS-appropriate app-support directory
// (~/Library/Application Support on macOS, verified directly against
// its adrg/xdg backing) -- the same convention Alfred/Raycast/1Password
// use for their own persisted settings, not something Mill invents.
// MILL_SETTINGS_PATH overrides this -- needed because server-mode and
// desktop-mode builds resolve to the exact same real path otherwise,
// which would let the Playwright e2e suite (server mode) write real
// composed workflows into the actual desktop dev app's saved state.
// playwright.config.ts points this at a throwaway temp file.
defaultSettingsPath := filepath.Join(application.Path(application.PathConfigHome), "mill", "settings.json")
settingsPath := os.Getenv("MILL_SETTINGS_PATH")
if settingsPath == "" {
settingsPath = defaultSettingsPath
}
settingsStore, err := settings.New(settingsPath)
if err != nil {
log.Fatal(err)
}
// docs/goals/0171-notification-spine.md: needs only settingsStore.
notificationService := notificationsvc.New(settingsStore)
compositionService := compositionsvc.NewCompositionService(settingsStore)
triggerService := triggersvc.NewTriggerService(compositionService, logger, settingsStore)
wiring.WireWorkflowLifecycle(compositionService, triggerService) // docs/goals/0250-workflow-delete-releases-hotkey.md
// MILL_TEST_KEYRING=memory swaps the OS keychain for a process-
// local store (e2e servers only): Linux CI has no Secret Service,
// so real-keychain semantics -- including "credential absent" --
// are otherwise untestable in server mode.
credentialStore := credential.New()
if os.Getenv("MILL_TEST_KEYRING") == "memory" {
credentialStore = credential.NewInMemory()
}
configureService := configuresvc.NewConfigureService(settingsStore, compositionService, credentialStore)
// MILL_SECRETS_PATH overrides for e2e isolation, same convention as MILL_SETTINGS_PATH above (WireSecrets' own doc comment covers the rest of what this wires).
secretService := wiring.WireSecrets(windowing.ConfigDirOrEnv("MILL_SECRETS_PATH", "secrets.kdbx"), credentialStore, configureService)
wiring.WireSecretRedaction(secretService) // goal 0185 S4
// docs/adr/0038: Atlas's storage/CRUD layer (cross-surface wiring
// arrives below via injected seams, never direct imports).
atlasService := atlassvc.NewAtlasService(settingsStore)
// docs/goals/0101 slice 1: the companion's own AI-provider lookup
// resolves through composition.ResolveAIProvider, which reads the
// SAME package-level seam configureService wires just below (no
// separate wiring call needed here).
companionService := companionsvc.NewCompanionService(atlasService)
// Separate SQLite file from settings.json (own schema, own lifecycle
// -- durable-execution checkpoints, not app config) but the same
// config-dir convention and the same MILL_* env-override shape
// settingsPath already established above, for the identical reason:
// desktop-mode and server-mode e2e runs must not share real state.
//
// MILL_EXECUTION_DATABASE_URL is a second, independent override: a
// full DBOS-native DSN (e.g. a Postgres/CockroachDB URL a regulated
// deployment points at its own audit database -- DBOS accepts this
// natively, confirmed against dbos.Config's own doc comment, no
// adapter change needed). Takes precedence over the path-based
// override below when set; MILL_EXECUTION_DB_PATH and the default
// sqlite path are otherwise unchanged.
executionDatabaseURL := os.Getenv("MILL_EXECUTION_DATABASE_URL")
if executionDatabaseURL == "" {
executionDatabaseURL = "sqlite:" + windowing.ConfigDirOrEnv("MILL_EXECUTION_DB_PATH", "execution.db")
}
// MILL_BACKUP_DIR follows the same override convention as above;
// docs/goals/0065's backupService construction reuses this value.
backupDir := windowing.ConfigDirOrEnv("MILL_BACKUP_DIR", "backups")
backupsvc.GuardVersionChange(logger, settingsStore, backupsvc.SQLiteDBPath(executionDatabaseURL), settingsPath, backupDir, millVersion)
mcpAuditService := wiring.WireAuditTrails(secretService, backupsvc.SQLiteDBPath(executionDatabaseURL), logger)
// goal 0234: composition and secretsvc's own audit store are wired
// together in wiring.WireClipboardHistory below, once secretService's
// OpenAudit call (mcpAuditService's own construction, above) has run.
clipboardHistoryService := clipboardhistorysvc.NewClipboardHistoryService(settingsStore)
wiring.WireClipboardHistory(clipboardHistoryService, secretService)
// MUST precede NewGuardrailService -- see MigrateLegacyPendingWrites'
// own doc comment for why the ordering here is load-bearing.
if err := mcpsvc.MigrateLegacyPendingWrites(settingsStore); err != nil {
logger.Error("migrate legacy MCP pending writes", "error", err)
}
guardrailService := guardrailsvc.NewGuardrailService(settingsStore, compositionService)
pluginService := wiring.NewPluginService(settingsPath, guardrailService, millChannel, millUpdateVersion)
// docs/goals/0240 S1: the coding loop's Confirm-screen preview --
// read-only over guardrailService.Rules(). Its ExecutionService
// dependency (goal 0240 S2, RunCommandBlock's own doc comment) is
// late-bound below via SetExecutionService, same shape
// TriggerService.SetExecutionService already uses just after this,
// since ExecutionService itself isn't constructed yet at this point.
codeLoopService := codeloopsvc.NewCodeLoopService(guardrailService)
executionService, err := executionsvc.NewExecutionService(executionDatabaseURL, compositionService, guardrailService)
if err != nil {
log.Fatal(err)
}
codeLoopService.SetExecutionService(executionService)
wiring.WireCodingLoopSecrets(codeLoopService, secretService)
wiring.WireCodingLoopEnvPreview(codeLoopService, compositionService, configureService) // docs/goals/0240 S4
// Single execution path (docs/adr/0008): a headless trigger fire now
// runs through the same durable ExecutionService.RunWorkflow every
// other entrypoint uses, tagged RunKindTriggered -- constructed after
// TriggerService (which needs comp at construction time for Sync's
// own workflow lookups) since ExecutionService itself depends on
// compositionService, so this can't be a constructor parameter
// without a cycle; same late-bound-setter shape as SetReservedCombo
// below.
triggerService.SetExecutionService(executionService)
// docs/adr/0010: a child-workflow node's real DBOS parent/child
// invocation, wired the same late-bound way for the same reason.
executionService.WireChildWorkflowRunner()
// docs/adr/0035's system-event dispatch seam, plus the notification
// spine's second consumer of it (docs/goals/0171-notification-spine.md).
wiring.WireSystemEventNotifications(executionService, triggerService, notificationService)
// goal 0052 slice 3: the version a run receipt's Build field stamps.
executionService.SetVersion(millVersion)
// docs/adr/0038 decision 4, goal 0061 slice C: "Update now" runs a
// card's referenced workflow through the normal guardrail-gated
// path -- same late-bound-setter shape as WireChildWorkflowRunner
// above, atlassvc never imports executionsvc directly. Kind is
// RunKindTriggered (production semantics: a disabled or
// never-published refresh workflow is rejected, same requirement
// child-workflow nodes already hold their callable target to).
atlassvc.SetWorkflowRunner(func(workflowID string) (string, bool, bool, error) {
summary, err := executionService.RunWorkflow(workflowID, executionsvc.RunKindTriggered, nil)
if err != nil {
return "", false, false, err
}
pending := summary.Pending != nil
return summary.RunID, !pending && summary.Status == "SUCCESS", pending, nil
})
// Card actions (goal 0084): source-card-recording entry, so the cycle guard covers action runs.
atlassvc.SetCardActionRunner(func(workflowID, sourceCardID string, values map[string]string, payload string) error {
_, err := executionService.RunWorkflowForAtlasCard(workflowID, sourceCardID, values, payload)
return err
})
executionService.SetRunCompletionSink(atlasService.NotifyRunCompleted)
atlasService.WireCompositionSeams(triggerService.DispatchAtlasCardChange) // goal 0066
// Cross-service seam adapters (recognition, List projection) live in the wiring package -- composition-root code split out of this file at the 500-line limit.
wiring.WireAtlasProjections(atlasService, configureService, compositionService)
wiring.WireValidationSeams(configureService)
wiring.WirePasteConversion(atlasService, configureService)
wiring.WireNotify()
backupService := backupsvc.Wire(backupsvc.SQLiteDBPath(executionDatabaseURL), settingsPath, backupDir, millVersion, compositionService, configureService, atlasService)
// docs/adr/0038, goal 0063/0067: the share model's mirror root, plus the image tool's captures folder (goal 0169 slice 2).
wiring.WireAtlasStorageDirs(atlasService)
atlasService.SetGuardedDataPaths(settingsPath, backupsvc.SQLiteDBPath(executionDatabaseURL), backupDir)
remoteAuthService := wiring.WireRemoteAuth(settingsStore, logger) // docs/goals/0132-remote-access.md SLICE 1
settingsService := settingssvc.NewSettingsService(settingsStore, triggerService, settingsPath != defaultSettingsPath)
wiring.WireSettingsEraSeams(settingsService, notificationService, remoteAuthService, triggerService, atlasService, pluginService)
settingsService.SetAppVersion(millUpdateVersion)
// The user's persisted channel opt-in wins over the build stamp --
// a source-built copy can deliberately follow the beta feed
// (Settings > Updates). Resolved once here so the guard, label,
// and provider feed below all agree for this run.
effectiveChannel := settingsService.ResolveUpdateChannel(millChannel)
settingsService.SetUpdateChannel(effectiveChannel)
// goal 0100: DownloadAndInstallUpdate's pre-swap snapshot seam.
settingsService.SetBackupRunner(backupService.BackupRunner())
// Bidirectional hotkey-conflict check (docs/SPEC.md §3.7): a
// per-workflow hotkey can't silently collide with the app-level
// summon hotkey, and vice versa -- SettingsService.AssignSummonHotkey
// already checks triggerService.ClaimedCombos() directly; this wires
// the other direction.
triggerService.SetReservedCombo(settingsService.ReservedCombo)
// docs/goals/0014-home-dashboard.md: Home's TimeSaved metric reads
// each workflow's user-editable "minutes saved per run" estimate,
// which SettingsService owns (its own persisted preference, §3.7) --
// wired in only now since ExecutionService is constructed before
// SettingsService exists, same late-bound-setter shape as
// SetReservedCombo just above.
executionService.SetMinutesSavedLookup(settingsService.GetWorkflowMinutesSaved)
// docs/SPEC.md §11 (task #12): Mill as MCP server, exposing its own
// workflows/Configure data as read-only Resources. Bind-address
// precedence (settingssvc.ResolveMCPAddr): the MILL_MCP_ADDR env
// ALWAYS wins (a deploy/env-level override, same MILL_* convention
// as settingsPath/executionDatabaseURL above), then the Settings >
// MCP access address, then the loopback default -- never 0.0.0.0
// unless explicitly chosen, since this is a new, unauthenticated
// local listener and staying loopback-bound is the conservative
// default until a real access-control need is named. A bind failure
// is logged, not fatal -- this is additive local tooling, not
// something the rest of the app depends on to function.
millMCPAddr, _ := settingssvc.ResolveMCPAddr(os.Getenv("MILL_MCP_ADDR"), settingsService.MCPAccessAddress())
millMCPService := mcpsvc.NewMillMCPService(millVersion, compositionService, configureService, settingsStore, userdocsFS, mcpAuditService.ServerMiddleware())
wiring.WireMillMCPService(millMCPService, settingsService, executionService, atlasService, mcpAuditService, guardrailService, millMCPAddr, logger)
agentLoopService := agentloopsvc.NewAgentLoopService(millMCPService) // an MCP client of it, ADR-0035
// Declared before application.New so the SingleInstance callback's
// closure (below) can capture it; assigned with `=` once the window
// is actually created further down. The callback only fires on a real
// second launch, long after mainWindow is set.
var mainWindow *application.WebviewWindow
app := application.New(application.Options{
Name: "mill",
Description: "Guardrailed agentic-workflow automation",
Logger: logger,
Services: []application.Service{
application.NewService(&capabilitysvc.CapabilitiesService{}),
application.NewService(compositionService),
application.NewService(triggerService),
application.NewService(configureService),
application.NewService(secretService),
application.NewService(atlasService),
application.NewService(companionService),
application.NewService(agentLoopService),
application.NewService(guardrailService),
application.NewService(pluginService),
application.NewService(clipboardHistoryService),
application.NewService(codeLoopService),
application.NewService(executionService),
application.NewService(settingsService),
application.NewService(backupService),
application.NewService(docssvc.New(userdocsFS)),
application.NewService(mcpAuditService),
application.NewService(remoteAuthService),
application.NewService(notificationService),
},
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
// Auth gate around the plugin asset route around the embedded
// bundle -- wiring.ComposedAssetMiddleware's own doc.
Middleware: wiring.ComposedAssetMiddleware(remoteAuthService, pluginService),
},
// Mill's macOS archetype and its termination contract live in the
// windowing adapter, where they are documented and pinned by a test
// (goal 0188) rather than sitting as bare option values here.
Mac: windowing.MacAppOptions(),
// Production-only single-instance guard (docs/SPEC.md §3.7's
// data-corruption hazard). nil in dev/server builds -- see
// singleinstance_{production,dev}.go for why the guard must NOT
// be active under `wails3 dev`. getMainWindow closes over
// mainWindow, assigned below after the window is created (the
// callback only ever fires on a real second launch, long after).
SingleInstance: singleInstanceOptions(func() *application.WebviewWindow { return mainWindow }),
})
// Wails3's own first-party self-updater (v3/pkg/updater) -- app.Updater
// is constructed by application.New() itself; the provider/Init
// wiring is extracted to settingssvc.InitUpdater (keeps this file
// under its own line-count convention). A construction failure here
// is logged, not fatal -- a broken updater must never block the app
// from starting.
if err := settingssvc.InitUpdater(app.Updater, "alicoding/mill", settingssvc.ResolveUpdateCurrentVersion(effectiveChannel, millUpdateVersion), effectiveChannel, settingsService); err != nil {
logger.Error("updater init", "error", err)
}
// Opt-in daily background check (goal 0122) -- a no-op unless the
// user enabled it in Settings; applies at boot like the channel
// preference.
settingsService.StartAutoUpdateChecks()
// Global hotkey registration needs the native run loop already
// spinning (docs/SPEC.md §2.2) -- ServiceStartup runs before the
// loop starts, so this waits for ApplicationStarted instead.
// TriggerService.Sync also registers the non-hotkey trigger types
// from here, for one uniform startup path.
app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) {
triggerService.Sync(compositionService.Workflows())
settingsService.RestoreSummonHotkey()
// docs/goals/0016-keymap-system.md: releases the native File >
// Close (⌘W) menu accelerator so that keypress falls through to
// the keymap's own command dispatch (tab.close) instead of being
// intercepted by NSMenu first -- must run before any hotkey
// recorder could ever call SuspendMenuAccelerators (see
// ReleaseMenuAccelerators's own doc comment for why the ordering
// matters). View > Reload (⌘R/⌘⇧R) is deliberately left alone --
// workflow.run's default is ⌘↩, not ⌘R (owner decision: ⌘R stays
// the native browser/dev reload).
settingsService.ReleaseMenuAccelerators()
// docs/adr/0032 §3: the away-user attention layer's OS-
// notification half. A failure here (a bare dev binary with no
// real bundle ID, or server mode) is logged, never fatal.
if err := settingsService.SetupAwayAttention(); err != nil {
logger.Warn("away-attention notifications setup", "error", err)
}
})
// Create a new window with the necessary options.
// 'Title' is the title of the window.
// 'Mac' options tailor the window when running on macOS.
// 'BackgroundColour' is the background colour of the window.
// 'URL' is the URL that will be loaded into the webview.
//
// Window sized to the golden ratio (1000 / 618 ≈ 1.618) by default --
// overridden by the persisted geometry from a previous session, if
// any (docs/SPEC.md §3.7's Update), applied here rather than moved
// after creation since there's no "move it after creation" path that
// avoids an initial flash at the default position/size.
windowWidth, windowHeight := 1000, 618
windowX, windowY := 0, 0
windowStartState := application.WindowStateNormal
hasPosition := false
if x, y, w, h, maximized, ok := settingsService.LoadWindowGeometry(); ok {
windowX, windowY, hasPosition = x, y, true
windowWidth, windowHeight = w, h
if maximized {
windowStartState = application.WindowStateMaximised
}
}
windowOptions := application.WebviewWindowOptions{
// Explicit name so tooling addressing windows by name (the MCP
// bridge's `window` parameter, internal/webviewbridgesmoke) can
// target the main window deterministically instead of relying on
// Wails's auto-generated "window-N" fallback.
Name: "main",
Title: "Mill",
Width: windowWidth,
Height: windowHeight,
// MinWidth/MinHeight are Wails3's own mechanism for "don't let the
// window shrink small enough to break the layout" (see
// docs/SPEC.md §2.2) -- not a custom guard. Floor chosen so the
// UnderlineNav tabs, a Runbook card's action row, and the footer
// all still fit without wrapping into each other.
MinWidth: 640,
MinHeight: 420,
StartState: windowStartState,
Mac: application.MacWindow{
InvisibleTitleBarHeight: 50,
Backdrop: application.MacBackdropTranslucent,
TitleBar: application.MacTitleBarHiddenInset,
},
BackgroundColour: application.NewRGB(6, 7, 15),
URL: "/",
// EnableFileDrop turns on Wails3's own native OS drag-and-drop:
// real absolute paths, delivered only to elements carrying
// data-file-drop-target (AtlasBoard.tsx, AtlasCardOverlay.tsx).
// In-app HTML5 drag (the creation tray, React Flow node drag) is
// unaffected -- the runtime only intercepts a 'Files' MIME type.
EnableFileDrop: true,
}
if hasPosition {
windowOptions.X = windowX
windowOptions.Y = windowY
// InitialPosition defaults to WindowCentered (its zero value) --
// X/Y are silently ignored unless this is explicitly set to
// WindowXY, confirmed directly against the SDK source before
// relying on it, not assumed.
windowOptions.InitialPosition = application.WindowXY
}
mainWindow = app.Window.NewWithOptions(windowOptions)
settingsService.SetWindow(windowing.WrapWindow(mainWindow))
settingsService.WatchWindowGeometry()
atlasService.WireFileDropWindow(windowing.WrapWindow(mainWindow))
launchatlogin.SetAutostartManager(app.Autostart)
// ADR-0033 second-window family, built in auxwindows.go.
settingsService.SetPanelWindow(windowing.WrapWindow(newQuickPanelWindow(app)))
settingsService.SetApprovalPromptWindow(windowing.WrapWindow(newApprovalPromptWindow(app)))
setupTray(app, settingsService)
// Create a goroutine that emits an event containing the current time every second.
// The frontend can listen to this event and update the UI accordingly.
go func() {
for {
now := time.Now().Format(time.RFC1123)
app.Event.Emit("time", now)
time.Sleep(time.Second)
}
}()
// Run the application. This blocks until the application has been exited.
err = app.Run()
wiring.RunShutdown(logger, executionService, backupService, millMCPService, mcpAuditService, atlasService, secretService)
// If an error occurred while running the application, log it and exit.
if err != nil {
log.Fatal(err)
}
}