@@ -135,6 +135,16 @@ type serverStatusMsg struct {
135135 Running bool
136136}
137137
138+ type promptThemeDepMsg struct {
139+ DepName string
140+ ShortName string
141+ }
142+
143+ type themeDepDecision struct {
144+ install bool
145+ method string
146+ }
147+
138148type tickMsg time.Time
139149
140150type memTickMsg struct {
@@ -171,6 +181,8 @@ const (
171181 PromptManagePackage
172182 PromptForceReinstallConfirm
173183 PromptSettings
184+ PromptThemeDepConfirm
185+ PromptThemeDepMethod
174186)
175187
176188var Program * tea.Program
@@ -246,6 +258,9 @@ type TuiModel struct {
246258 finalizedAdds map [string ]string // pkgName -> method
247259 finalizedRemoves []string // pkgNames
248260 finalizedTheme * string // theme name to activate
261+
262+ themeDepResolveChan chan themeDepDecision
263+ activeThemeDep string
249264}
250265
251266func NewModel (root string ) TuiModel {
@@ -266,6 +281,7 @@ func NewModel(root string) TuiModel {
266281 pendingPackages : make (map [string ]bool ),
267282 pendingTheme : nil ,
268283 finalizedAdds : make (map [string ]string ),
284+ themeDepResolveChan : make (chan themeDepDecision , 1 ),
269285 }
270286}
271287
@@ -899,6 +915,29 @@ func (m TuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
899915 } else {
900916 m .statusMsg = "All packages are up to date."
901917 }
918+ case promptThemeDepMsg :
919+ m .activePrompt = PromptThemeDepConfirm
920+ m .activeThemeDep = msg .DepName
921+ m .promptSel = 0 // default to Yes (0: Yes, 1: No)
922+
923+ // Find catalog entry for this dep
924+ var entry * bridge.CatalogEntry
925+ if m .catalog != nil {
926+ for _ , e := range m .catalog .Entries {
927+ if e .Name == msg .DepName {
928+ entry = & e
929+ break
930+ }
931+ }
932+ }
933+ if entry == nil {
934+ entry = & bridge.CatalogEntry {
935+ Name : msg .DepName ,
936+ ShortName : msg .ShortName ,
937+ Kind : "module" ,
938+ }
939+ }
940+ m .promptPkg = entry
902941 return m , nil
903942
904943 case logLineMsg :
@@ -953,10 +992,14 @@ func (m *TuiModel) addLog(line string) {
953992func (m TuiModel ) handlePromptKeys (msg tea.KeyMsg ) (tea.Model , tea.Cmd ) {
954993 switch msg .String () {
955994 case "esc" , "q" :
995+ if m .activePrompt == PromptThemeDepConfirm || m .activePrompt == PromptThemeDepMethod {
996+ m .themeDepResolveChan <- themeDepDecision {install : false }
997+ }
956998 m .activePrompt = PromptNone
957999 m .promptPkg = nil
9581000 m .promptQueue = nil
9591001 m .promptQueueIndex = 0
1002+ m .activeThemeDep = ""
9601003 return m , nil
9611004 case "up" , "k" :
9621005 if m .activePrompt == PromptManagePackage {
@@ -985,9 +1028,9 @@ func (m TuiModel) handlePromptKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
9851028 }
9861029 } else {
9871030 limit := 2
988- if m .activePrompt == PromptUninstallConfirm || m .activePrompt == PromptForceReinstallConfirm {
1031+ if m .activePrompt == PromptUninstallConfirm || m .activePrompt == PromptForceReinstallConfirm || m . activePrompt == PromptThemeDepConfirm {
9891032 limit = 1
990- } else if m .activePrompt == PromptInstallMethod && m .promptPkg != nil {
1033+ } else if ( m .activePrompt == PromptInstallMethod || m . activePrompt == PromptThemeDepMethod ) && m .promptPkg != nil {
9911034 limit = len (m .getInstallMethods (m .promptPkg )) - 1
9921035 }
9931036 if m .promptSel < limit {
@@ -999,7 +1042,7 @@ func (m TuiModel) handlePromptKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
9991042 if m .settingsSel == 5 {
10001043 m .settingsSel = 4
10011044 }
1002- } else if m .activePrompt != PromptManagePackage && m .activePrompt != PromptInstallMethod {
1045+ } else if m .activePrompt != PromptManagePackage && m .activePrompt != PromptInstallMethod && m . activePrompt != PromptThemeDepMethod {
10031046 if m .promptSel > 0 {
10041047 m .promptSel --
10051048 }
@@ -1009,9 +1052,9 @@ func (m TuiModel) handlePromptKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
10091052 if m .settingsSel == 4 {
10101053 m .settingsSel = 5
10111054 }
1012- } else if m .activePrompt != PromptManagePackage && m .activePrompt != PromptInstallMethod {
1055+ } else if m .activePrompt != PromptManagePackage && m .activePrompt != PromptInstallMethod && m . activePrompt != PromptThemeDepMethod {
10131056 limit := 2
1014- if m .activePrompt == PromptUninstallConfirm || m .activePrompt == PromptForceReinstallConfirm {
1057+ if m .activePrompt == PromptUninstallConfirm || m .activePrompt == PromptForceReinstallConfirm || m . activePrompt == PromptThemeDepConfirm {
10151058 limit = 1
10161059 }
10171060 if m .promptSel < limit {
@@ -1022,6 +1065,41 @@ func (m TuiModel) handlePromptKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
10221065 pkg := m .promptPkg
10231066 prompt := m .activePrompt
10241067
1068+ if prompt == PromptThemeDepConfirm {
1069+ if m .promptSel == 0 { // Yes
1070+ m .activePrompt = PromptThemeDepMethod
1071+ methods := m .getInstallMethods (pkg )
1072+ selIdx := 0
1073+ for idx , mth := range methods {
1074+ if mth .Name == m .lastInstallMethod {
1075+ selIdx = idx
1076+ break
1077+ }
1078+ }
1079+ m .promptSel = selIdx
1080+ return m , nil
1081+ } else { // No
1082+ m .activePrompt = PromptNone
1083+ m .promptPkg = nil
1084+ m .activeThemeDep = ""
1085+ m .themeDepResolveChan <- themeDepDecision {install : false }
1086+ return m , nil
1087+ }
1088+ } else if prompt == PromptThemeDepMethod {
1089+ m .activePrompt = PromptNone
1090+ m .promptPkg = nil
1091+ m .activeThemeDep = ""
1092+ methods := m .getInstallMethods (pkg )
1093+ if m .promptSel >= 0 && m .promptSel < len (methods ) {
1094+ selectedMethod := methods [m .promptSel ].Name
1095+ m .lastInstallMethod = selectedMethod
1096+ m .themeDepResolveChan <- themeDepDecision {install : true , method : selectedMethod }
1097+ } else {
1098+ m .themeDepResolveChan <- themeDepDecision {install : false }
1099+ }
1100+ return m , nil
1101+ }
1102+
10251103 if len (m .promptQueue ) > 0 {
10261104 if prompt == PromptUninstallConfirm {
10271105 if m .promptSel == 0 { // Yes
@@ -2571,6 +2649,46 @@ func (m TuiModel) renderModal() string {
25712649 }
25722650 content .WriteString ("\n " + subtleStyle .Render ("↑↓ select Enter confirm Esc cancel" ))
25732651 return modalStyle .Width (72 ).Render (content .String ())
2652+ } else if m .activePrompt == PromptThemeDepMethod {
2653+ content .WriteString (boldStyle .Render ("Install source for theme dependency: " ) + accentStyle .Render (pkg .ShortName ) + "\n \n " )
2654+ methods := m .getInstallMethods (pkg )
2655+ for i , mth := range methods {
2656+ if i > 0 {
2657+ if mth .Name != "local" && methods [i - 1 ].Name == "local" {
2658+ content .WriteString ("\n " + subtleStyle .Render (strings .Repeat ("─" , 62 )) + "\n \n " )
2659+ } else {
2660+ content .WriteString ("\n " )
2661+ }
2662+ }
2663+ var btn string
2664+ if i == m .promptSel {
2665+ btn = modalOptionActive .Render (mth .Label )
2666+ } else {
2667+ btn = modalOptionInactive .Render (mth .Label )
2668+ }
2669+ content .WriteString (" " + btn + "\n " )
2670+ content .WriteString (" " + mutedStyle .Render (mth .Desc ) + "\n " )
2671+ }
2672+ content .WriteString ("\n " + subtleStyle .Render ("↑↓ select Enter confirm Esc cancel" ))
2673+ return modalStyle .Width (72 ).Render (content .String ())
2674+ } else if m .activePrompt == PromptThemeDepConfirm {
2675+ content .WriteString (boldStyle .Render ("Theme dependency required: " ) + accentStyle .Render (pkg .ShortName ) + "\n " )
2676+ content .WriteString (subtleStyle .Render ("The active or queued theme requires this package to function correctly." ) + "\n \n " )
2677+ content .WriteString (boldStyle .Render ("Install " ) + accentStyle .Render (pkg .ShortName ) + boldStyle .Render ("?" ) + "\n \n " )
2678+ opts := []string {"Yes, install" , "No, skip" }
2679+ for i , opt := range opts {
2680+ if i == m .promptSel {
2681+ bg := colorAccent
2682+ if i == 1 {
2683+ bg = colorErr
2684+ }
2685+ content .WriteString (lipgloss .NewStyle ().Background (bg ).Foreground (lipgloss .Color ("#000000" )).Bold (true ).Padding (0 , 2 ).Render (" " + opt + " " ))
2686+ } else {
2687+ content .WriteString (modalOptionInactive .Render (" " + opt + " " ))
2688+ }
2689+ content .WriteString (" " )
2690+ }
2691+ content .WriteString ("\n \n " + subtleStyle .Render ("← → select Enter confirm Esc cancel" ))
25742692 } else if m .activePrompt == PromptUninstallConfirm {
25752693 content .WriteString (boldStyle .Render ("Uninstall " ) + errStyle .Render (pkg .ShortName ) + boldStyle .Render ("?" ) + "\n \n " )
25762694 opts := []string {"Yes, uninstall" , "No, keep it" }
@@ -2941,33 +3059,51 @@ func (m *TuiModel) RunSetupTask(adds map[string]string, p *tea.Program) {
29413059 }
29423060 }
29433061 }
3062+ if ! installed {
3063+ if _ , exists := adds [dep ]; exists {
3064+ installed = true
3065+ }
3066+ }
29443067
29453068 if ! installed {
29463069 depShort := dep
29473070 if idx := strings .LastIndex (dep , "/" ); idx >= 0 {
29483071 depShort = dep [idx + 1 :]
29493072 }
29503073
2951- p .Send (logLineMsg (fmt .Sprintf (">>> Installing theme dependency: %s…" , depShort )))
2952- depArgs := []string {desktopJs , "add" , depShort }
2953- if method == "npm" {
2954- depArgs = append (depArgs , "--npm" )
2955- } else {
2956- depOwner := "owdproject"
2957- if m .ctx != nil && m .ctx .Settings .GithubUser != nil && * m .ctx .Settings .GithubUser != "" {
2958- depOwner = * m .ctx .Settings .GithubUser
2959- }
2960-
2961- var fromVal string
2962- if method == "git-ssh" {
2963- fromVal = fmt .Sprintf ("git@github.com:%s/%s.git" , depOwner , depShort )
3074+ p .Send (logLineMsg (fmt .Sprintf (">>> Theme dependency detected: %s" , depShort )))
3075+ p .Send (promptThemeDepMsg {
3076+ DepName : dep ,
3077+ ShortName : depShort ,
3078+ })
3079+
3080+ decision := <- m .themeDepResolveChan
3081+ if decision .install {
3082+ p .Send (logLineMsg (fmt .Sprintf (">>> Installing theme dependency: %s via %s…" , depShort , decision .method )))
3083+ depArgs := []string {desktopJs , "add" , depShort }
3084+ if decision .method == "npm" {
3085+ depArgs = append (depArgs , "--npm" )
3086+ } else if decision .method == "local" {
3087+ depArgs = append (depArgs , "--dev" )
29643088 } else {
2965- fromVal = fmt .Sprintf ("https://github.com/%s/%s.git" , depOwner , depShort )
3089+ depOwner := "owdproject"
3090+ if m .ctx != nil && m .ctx .Settings .GithubUser != nil && * m .ctx .Settings .GithubUser != "" {
3091+ depOwner = * m .ctx .Settings .GithubUser
3092+ }
3093+
3094+ var fromVal string
3095+ if decision .method == "git-ssh" {
3096+ fromVal = fmt .Sprintf ("git@github.com:%s/%s.git" , depOwner , depShort )
3097+ } else {
3098+ fromVal = fmt .Sprintf ("https://github.com/%s/%s.git" , depOwner , depShort )
3099+ }
3100+ depArgs = append (depArgs , "--from" , fromVal )
29663101 }
2967- depArgs = append (depArgs , "--from" , fromVal )
2968- }
2969- if err := runProcessAndStreamLogsSilent (m .workspaceRoot , "node" , depArgs , p ); err != nil {
2970- p .Send (logLineMsg (fmt .Sprintf (">>> Dependency %s failed: %v" , depShort , err )))
3102+ if err := runProcessAndStreamLogsSilent (m .workspaceRoot , "node" , depArgs , p ); err != nil {
3103+ p .Send (logLineMsg (fmt .Sprintf (">>> Dependency %s failed: %v" , depShort , err )))
3104+ }
3105+ } else {
3106+ p .Send (logLineMsg (fmt .Sprintf (">>> Skipped installing theme dependency: %s" , depShort )))
29713107 }
29723108 }
29733109 }
0 commit comments