Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d7f4979
Add Linear OAuth sync integration and macOS UX
roodboi Mar 5, 2026
ebbc60c
Finish Linear sync phase 2 and desktop review flow
roodboi Mar 5, 2026
b65955b
Finish Linear broker ownership and review queue
roodboi Mar 6, 2026
7a5637f
Start Linear phase 3 ownership schema work
roodboi Mar 6, 2026
781d638
Finish Linear authz, routing, and review flow
roodboi Mar 6, 2026
29477c9
Add Linear autosync executor and desktop sync controls
roodboi Mar 6, 2026
da2526c
Polish desktop Linear routing and ticket UX
roodboi Mar 6, 2026
04ab89c
Add Hack account auth and provider UX design docs
roodboi Mar 6, 2026
e9dec4f
Plan remote encrypted env portability next slice
roodboi Mar 6, 2026
6b50d7d
Add Better Auth session shell routes
roodboi Mar 6, 2026
da9aa64
Refine macOS auth and settings UX
roodboi Mar 6, 2026
41855ea
Add Hack auth CLI commands
roodboi Mar 6, 2026
a60d5d0
Add Hack account auth shell and desktop UX
roodboi Mar 6, 2026
fdc63be
Refine Hack Desktop account and settings UX
roodboi Mar 6, 2026
a6b24a4
Simplify Hack account UX and shared refresh state
roodboi Mar 6, 2026
5dcdcba
Calm desktop settings account and routing UX
roodboi Mar 6, 2026
cc64baa
Fix Hack auth browser login handoff
roodboi Mar 6, 2026
2a98940
Fix Hack auth login flow and polish auth shell
roodboi Mar 6, 2026
672e306
Flatten Hack auth handoff shell
roodboi Mar 6, 2026
d8c430a
Simplify Hack auth sign-in screen
roodboi Mar 6, 2026
b0cd238
Polish Hack auth GitHub handoff button
roodboi Mar 6, 2026
bcbb390
Unify Hack auth shell and isolate debug deep links
roodboi Mar 7, 2026
62f06fe
Fix Linear remote connection persistence and repair UX
roodboi Mar 7, 2026
f58c5d1
Add Linear browser handoff design and plan docs
roodboi Mar 7, 2026
5d70dfe
Enable hack linear extension
roodboi Mar 8, 2026
29e7758
Unify Linear browser handoff and repair callback flow
roodboi Mar 7, 2026
4f574f4
Bind Linear OAuth flows to Hack auth ownership
roodboi Mar 7, 2026
7fcec0f
Serve provider callback errors without edge 502s
roodboi Mar 7, 2026
f9b2b6e
Fix desktop Linear OAuth handoff start flow
roodboi Mar 7, 2026
e955826
docs: plan linear local access seeding
roodboi Mar 7, 2026
06cc4a9
Seed local Linear access from Hack-owned connections
roodboi Mar 7, 2026
4cfc3ba
Fix Linear local access seeding on main branch
roodboi Mar 8, 2026
cddb899
Send Bearer auth to Linear APIs
roodboi Mar 8, 2026
0e9a222
Fix Linear GraphQL callback lookup
roodboi Mar 8, 2026
735961f
Fix false Linear repair state in desktop settings
roodboi Mar 8, 2026
aa0eed6
Recover from stale tickets git index locks
roodboi Mar 8, 2026
3e1641b
Fix Linear project routing selector state
roodboi Mar 8, 2026
03bd184
Fix Linear project ticket settings UX
roodboi Mar 8, 2026
83fe2d9
Inline Linear ticket sync project settings
roodboi Mar 8, 2026
57bdd47
Stabilize inline Linear project binding
roodboi Mar 8, 2026
638c6bb
Align Linear project label with picker state
roodboi Mar 8, 2026
55f29ed
Fix Linear project bind config writes
roodboi Mar 8, 2026
93f2566
Fix Linear pull query and ticket ref retry
roodboi Mar 8, 2026
a370547
Fix tickets ref fallback during Linear sync
roodboi Mar 9, 2026
aa98914
Add tickets ref repair to doctor
roodboi Mar 9, 2026
9749f70
Stabilize desktop ticket sync state
roodboi Mar 9, 2026
f61e889
Add autosync-first tickets design docs
roodboi Mar 9, 2026
4683adf
Make desktop tickets autosync-first
roodboi Mar 9, 2026
0a99bf8
Polish desktop tickets autosync UX
roodboi Mar 9, 2026
e8f6c08
Review PR fixes for install issues
roodboi Mar 9, 2026
38b47d4
Review doctor host DNS fix
roodboi Mar 9, 2026
d480646
Review PR fixes and ticket
roodboi Mar 9, 2026
065b0c7
Review PR fixes and CI issues
roodboi Mar 9, 2026
b9d698f
Fix CI failures and update PR
roodboi Mar 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .hack/hack.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"extensions": {
"dance.hack.tickets": {
"enabled": true
},
"dance.hack.linear": {
"enabled": true
}
},
"gateway": {
Expand Down
9 changes: 5 additions & 4 deletions apps/macos/App/HackDesktopApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,10 @@ struct HackDesktopApp: App {

@MainActor
private func handleIncomingDeepLink(url: URL) {
guard model.ingestGitHubOAuthDeepLink(url: url) else {
return
}
let handledHackAuth = model.ingestHackAuthDeepLink(url: url)
let handledGitHub = model.ingestGitHubOAuthDeepLink(url: url)
let handledLinear = model.ingestLinearOAuthDeepLink(url: url)
guard handledHackAuth || handledGitHub || handledLinear else { return }

#if os(macOS)
NSApp.activate(ignoringOtherApps: true)
Expand All @@ -119,7 +120,7 @@ struct HackDesktopApp: App {
name: .hackSettingsRequested,
object: nil,
userInfo: [
"pane": "github",
"pane": handledHackAuth ? "account" : (handledLinear ? "linear" : "github"),
]
)
}
Expand Down
8 changes: 7 additions & 1 deletion apps/macos/App/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@
<string>dance.hack.desktop</string>
<key>CFBundleURLSchemes</key>
<array>
<string>hack</string>
<string>$(HACK_URL_SCHEME)</string>
</array>
</dict>
</array>
<key>HackAuthCompletionURL</key>
<string>$(HACK_URL_SCHEME)://auth/complete</string>
<key>HackGitHubOAuthCallbackURL</key>
<string>$(HACK_URL_SCHEME)://auth/github/callback</string>
<key>HackLinearOAuthCallbackURL</key>
<string>$(HACK_URL_SCHEME)://auth/linear/callback</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
Expand Down
1 change: 1 addition & 0 deletions apps/macos/Config/Base.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CODE_SIGN_ENTITLEMENTS = App/HackDesktop.entitlements

PRODUCT_NAME = Hack Desktop
PRODUCT_BUNDLE_IDENTIFIER = dance.hack.desktop
HACK_URL_SCHEME = hack
MACOSX_DEPLOYMENT_TARGET = 14.0
MARKETING_VERSION = 1.17.0
CURRENT_PROJECT_VERSION = $(MARKETING_VERSION)
1 change: 1 addition & 0 deletions apps/macos/Config/Debug.xcconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "Base.xcconfig"

HACK_URL_SCHEME = hack-dev
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ import HackDesktopModels
extension Notification.Name {
public static let hackCommandPaletteRequested = Notification.Name("hack.commandPalette.requested")
public static let hackRefreshRequested = Notification.Name("hack.refresh.requested")
public static let hackProjectNavigationRequested = Notification.Name("hack.projectNavigation.requested")
public static let hackTicketReviewQueueRequested = Notification.Name("hack.ticketReviewQueue.requested")
public static let hackProjectRoutingRequested = Notification.Name("hack.projectRouting.requested")
}

enum ProjectNavigationRequest {
static let projectIdKey = "projectId"
static let tabKey = "tab"
static let sidebarKey = "sidebar"
}

enum TicketReviewQueueRequest {
static let projectIdKey = "projectId"
}

enum ProjectRoutingRequest {
static let projectIdKey = "projectId"
}

struct CommandPaletteAction: Identifiable {
Expand Down Expand Up @@ -74,6 +91,16 @@ struct CommandPaletteView: View {
}
)

actions.append(
CommandPaletteAction(title: "Go to Linear Settings", subtitle: "Connected accounts + sync policy") {
NotificationCenter.default.post(
name: .hackSettingsRequested,
object: nil,
userInfo: [SettingsNavigationRequest.paneKey: SettingsSidebarItem.linear.rawValue]
)
}
)

actions.append(
CommandPaletteAction(title: "Go to Runtime Settings", subtitle: "System status + daemon") {
NotificationCenter.default.post(
Expand Down Expand Up @@ -134,6 +161,32 @@ struct CommandPaletteView: View {
model.selectedItem = .project(project.id)
}
)
if project.supportsTickets {
actions.append(
CommandPaletteAction(
title: "Open Tickets: \(project.name)",
subtitle: "Ticket board + manual sync tools"
) {
openProjectTickets(project)
}
)
actions.append(
CommandPaletteAction(
title: "Open Review Queue: \(project.name)",
subtitle: "Conflicts, comments, and merge follow-up"
) {
openProjectTicketReviewQueue(project)
}
)
actions.append(
CommandPaletteAction(
title: "Open Linear routing: \(project.name)",
subtitle: "Project account + bound Linear project"
) {
openProjectRouting(project)
}
)
}
}

if let project = model.selectedProject {
Expand All @@ -149,7 +202,12 @@ struct CommandPaletteView: View {
}
actions.append(
CommandPaletteAction(title: "Project: Overview", subtitle: project.name) {
model.selectedProjectTab = .overview
openProjectOverview(project)
}
)
actions.append(
CommandPaletteAction(title: "Project: Linear routing", subtitle: project.name) {
openProjectRouting(project)
}
)
if project.isRuntimeConfigured {
Expand Down Expand Up @@ -181,7 +239,15 @@ struct CommandPaletteView: View {
if project.supportsTickets {
actions.append(
CommandPaletteAction(title: "Project: Tickets", subtitle: project.name) {
model.selectedProjectTab = .tickets
openProjectTickets(project)
}
)
actions.append(
CommandPaletteAction(
title: "Project: Review queue",
subtitle: project.name
) {
openProjectTicketReviewQueue(project)
}
)
}
Expand Down Expand Up @@ -216,6 +282,27 @@ struct CommandPaletteView: View {
Task { _ = await model.syncTickets(for: project) }
}
)
actions.append(
CommandPaletteAction(title: "Tickets: Pull from Linear", subtitle: project.name) {
Task {
_ = await model.syncLinearProject(
for: project,
from: "linear"
)
}
}
)
actions.append(
CommandPaletteAction(title: "Tickets: Push Hack to Linear", subtitle: project.name) {
Task {
_ = await model.syncLinearProject(
for: project,
from: "hack",
ownerMode: "hack"
)
}
}
)
actions.append(
CommandPaletteAction(title: "Tickets: Setup", subtitle: project.name) {
Task { _ = await model.setupTickets(for: project) }
Expand Down Expand Up @@ -244,6 +331,38 @@ struct CommandPaletteView: View {
return nil
}

private func openProjectOverview(_ project: ProjectSummary) {
model.selectedItem = .project(project.id)
model.selectedProjectTab = .overview
}

private func openProjectTickets(_ project: ProjectSummary) {
model.selectedItem = .project(project.id)
model.selectedProjectTab = .tickets
}

private func openProjectTicketReviewQueue(_ project: ProjectSummary) {
openProjectTickets(project)
DispatchQueue.main.async {
NotificationCenter.default.post(
name: .hackTicketReviewQueueRequested,
object: nil,
userInfo: [TicketReviewQueueRequest.projectIdKey: project.id]
)
}
}

private func openProjectRouting(_ project: ProjectSummary) {
model.selectedItem = .project(project.id)
NotificationCenter.default.post(
name: .hackProjectRoutingRequested,
object: nil,
userInfo: [
ProjectRoutingRequest.projectIdKey: project.id
]
)
}

private func openPreferredCodingAgent(for project: ProjectSummary, path: String) {
let command = CodingAgentIntegration.launchCommand(
projectPath: path,
Expand Down
Loading
Loading