Skip to content

feat: add workspace app icons to tray window #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

deansheather
Copy link
Member

@deansheather deansheather commented May 6, 2025

  • Adds AgentAppViewModel to handle each button
  • Adds collapsible control components to handle the collapsing section
  • Adds Uuid type to work around issues with the built-in Guid type
  • Adds ModelMerge utility for merging lists with minimal updates to work around constant flashing in the UI

TODO:

  • Auto expand the first agent (to match macOS)
  • Only allow one agent to be expanded at a time (to match macOS)
  • Add tests for ModelMerge
  • Get rid of the SVG size constants in AgentAppViewModel since I don't think they do anything
  • Add $SESSION_TOKEN replacement
  • Display icons for vscode and vscode insiders

Known issues:

  • /icon/cursor.svg seems to be an SVG with an embedded PNG, which I guess can't be rendered properly by WinUI
buttons.mp4

Closes #50

- Adds AgentAppViewModel to handle each button
- Adds collapsible control components to handle the collapsing section
- Adds Uuid type to work around issues with the built-in Guid type
- Adds ModelMerge utility for merging lists with minimal updates to work
  around constant flashing in the UI
Copy link
Collaborator

@spikecurtis spikecurtis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't finish looking at all the files, but it looks like you're still doing some stuff, so I'll get another look later anyway. It's the end of my day so sending the comments I have so you'll have them to look at in the morning.


// Sort by status green, red, gray, then by hostname.
agents.Sort((a, b) =>
ModelUpdate.ApplyLists(Agents, agents, (a, b) =>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this application will propagate the PropertyChanged event, which means that existing agents won't collapse new agents when they are expanded.

Having agent view models collapse other agent view models feels fragile. Could we have some state on this view model that controls which agent is expanded, rather than a bunch of IsExpanded bools on each agent view model that we need to toggle? Or, could we lift the routine that expands an agent up to this model, so that it can expand exactly one of them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, on the mac app we just modify isExpanded: UUID? to do this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this application will propagate the PropertyChanged event, which means that existing agents won't collapse new agents when they are expanded.

It won't propagate it, but every single agent that was created will get the event listener added exactly once when created.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, whoops, I missed that you are iterating over Agents not agents. You're right that each new agent gets the listener, but now there could be concurrent access to Agents while you are modifying them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There shouldn't be concurrent access either since we're doing all of this in the UI thread (PropertyChanged gets called immediately when setting an observable property, which can only happen in the UI thread), and all changes to Agents also happen in the UI thread.

I agree it's not pretty to do it this way, but I think the idea of passing down the ID of the currently expanded item to each child is also not pretty IMO. It works better in Swift since you can safely have a pointer to an ID shared between all AgentViewModels, but you can't do that in C# without using unsafe or wrapping the ID in a class and using that as a reference. I thought about implementing it with a binding but since we're not creating these view models in the XAML code we have to write the bindings ourself which is not pretty.

Since we're using an ItemsRepeater to render these agents, the data context changes and we can't access the TrayWindowViewModel to e.g. set the command handler for the button to be a method on the parent view model unless we pass TrayWindowViewModel to the child AgentViewModel, which is also not pretty.

Every method of doing this seems to suck in a different way unfortunately. The reason I went this way originally was so none of the AgentViewModels had to be aware of any of the other AgentViewModels.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spike and I discussed in a call and we've opted to:

  1. Create a new interface IAgentExpanderHost which is implemented by TrayWindowViewModel
  2. Pass the TrayWindowViewModel as an IAgentExpanderHost into all of the AgentViewModels
  3. Have each of the AgentViewModels call the method on the interface when this.IsExpanded changes
  4. The implementation on TrayWindowViewModel will collapse every other agent by calling agent.SetExpanded(false)


public partial class CoderApiClient
{
public Task<WorkspaceAgent> GetWorkspaceAgent(string id, CancellationToken ct = default)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be safer if we accepted a UUID, rather than a string, which could totally change the endpoint we hit.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered this but didn't want to add a dependency between CoderSdk and Vpn.Proto, and adding a new project for just a UUID type seems overkill. The Go codersdk package accepts string IDs for some methods as well, so I think it's fine.

@deansheather deansheather requested a review from spikecurtis May 9, 2025 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Button Auto-Population for External Apps
3 participants