Skip to content

Expose the 'default' workspace relationship as a toggleable interest badge on workspaces #1137

Description

@JoshuaRowePhantom

Summary

Expose the "default" workspace relationship as a toggleable interest badge on workspace entity cards, so a user can mark/unmark a workspace as their default for the current user-computer profile from the standard interest-toggle UI (the same affordance used for actionable, blocked, etc.). Today a "default workspace" is modeled by a default relationship (participants.applied-to = the user-computer profile, participants.value = the workspace) that is read at startup to auto-open the default workspace, but there is no UI to set or clear it — it can only be seeded as data.

Rather than special-casing default in the interest code, this enhancement generalises the interest mechanism to be data-driven: an interest-type definition declares which participant references the badged entity (target-participant) and which participant(s) scope it and to which session identity they bind (applies-to). These two attributes are required on every interest-type (there are no implicit defaults in code): the existing interests (actionable, blocked, assigned-to, not-interesting) are updated to declare their current {target, user} shape explicitly, and default registers with target-participant: "value" and an applies-to binding its applied-to participant to the current user-computer-profile. The same generic projector/toggle then serves every interest with no per-name branching.

Design note (per issue feedback): the new attributes are required, not optional/defaulted — each existing interest definition is edited to supply them. There is no singleton behavior: toggling default on simply creates a default relationship for the current profile, and toggling off deletes it; the feature does not auto-clear a profile's other defaults.

This is a feature/enhancement, not a defect.

Current State / Root Cause

The "default" relationship (how a default workspace is modeled today)

A default workspace is a relationship entity of type default. Its schema requires applied-to and value participants (NOT the generic target/user shape used by interests):

Phantom.Workspaces.Data.Core/JsonSchemas/default.json (participants block)

"participants": {
  "properties": {
    "applied-to": { "$ref": "core.json#/$defs/entity-id" },
    "value":      { "$ref": "core.json#/$defs/entity-id" }
  },
  "required": ["applied-to", "value"],
  "additionalProperties": false
}

The relationship-type definition entity is default-entity-type.json (entity-id b1d4f3a2-8c5e-4b7d-9f6a-1e2d3c4b5a6f), currently typed only as entity/entity-type/json-schema/noteneither interest-type nor relationship-type:

Phantom.Workspaces.Data.Core/JsonEntities/schema-definitions/default-entity-type.json

"entity-types": [ "entity", "entity-type", "json-schema", "note" ],
"names": [
  ["json-schemas", "https://schemas.workspaces.phantom.to/workspaces/data/core/default.json"],
  ["entity-types", "default"]
]

The default workspace is read at startup and on closing the last workspace, keyed on the current user-computer profile:

Phantom.Workspaces/ViewModels/MainWindowViewModel.cs:3501-3557 (QueryDefaultWorkspaceIdsAsync)

var profileId = broker.EntityRepository.WorkspaceEntitySession.UserComputerProfileEntityId;
...
new EntityTypeQueryClause { EntityTypeNames = new EntityTypeNameSet(["default"]) },
new EntityFieldQueryClause
{
    FieldPath = new FieldPath("participants", "applied-to"),
    ComparisonOperator = FieldComparisonOperator.Equals,
    Value = JsonSerializer.SerializeToElement(profileId.Value.ToString()),
}
// ... reads participants.value -> workspace EntityId

The read path returns every default whose applied-to == currentProfile (QueryDefaultWorkspaceIdsAsync collects a list of workspace ids). Callers at MainWindowViewModel.cs:3410 (initialize / open default) and :3483 (fall back to default when the last workspace closes). Nothing writes this relationship from the UI — it is data-only today.

Scope semantics. The default is per user-computer profile: the read path is keyed on applied-to == currentProfile. Any profile may independently mark a workspace as its default, and marking a default for one profile must not disturb another profile's default.

The "interest" mechanism (how toggleable badges work today)

An interest type is defined by an interest-type entity carrying applied/notApplied badge metadata and optional display-entity-types. Its schema is:

Phantom.Workspaces.Data.Core/JsonSchemas/interest-type.jsonallOf relationship-type.json; required entity-types, applied, notApplied. There is no top-level unevaluatedProperties:false/additionalProperties:false (only the nested interest-state-content def sets unevaluatedProperties:false), so additional declared top-level properties are permitted.

InterestCatalog discovers interest types via a live query for entity-types = interest-type, and exposes their names centrally:

  • Definition record: Phantom.Workspaces/InterestCatalog.cs:17-25InterestTypeDefinition(Name, AppliedGlyph, NotAppliedGlyph, AppliedDescription, NotAppliedDescription, AppliedActionText, NotAppliedActionText, DisplayEntityTypes) (8 positional fields).
  • JSON parsing: InterestCatalog.cs:115-138 (TryReadInterestType)
  • Live discovery: InterestCatalog.cs:63-80 (CreateAsync, subscribes to interest-type entities)
  • Central name set: InterestCatalog.cs:57 (InterestTypeNames)
  • Definition entities (each already carries "interest-type" + "relationship-type"): actionable-entity-type.json, blocked-entity-type.json, assigned-to-entity-type.json, not-interesting-entity-type.json. Their relationship schemas (actionable.json, blocked.json, assigned-to.json, not-interesting.json) use participants { target, user } (plus optional view for actionable/blocked), required ["target", "user"], user constrained to x-entity-types: ["user"].

Badges are projected per entity by InterestBadgeProjector.Project (Phantom.Workspaces/InterestBadgeProjector.cs:18-39), which:

  • decides applicability per entity type via ShouldShowBadge using display-entity-types / display-interest-types (InterestBadgeProjector.cs:41-97), and
  • decides applied vs not via GetAppliedInterestsIsTargetOf, which hard-codes participants.target == entity (InterestBadgeProjector.cs:113-149). It does not check the user participant, so applied-detection is not scoped to the current identity today.

Toggling is done by InterestToggle.ToggleAsync(EntityBroker, EntitySnapshot, string interestTypeName, CancellationToken) (Phantom.Workspaces/InterestToggle.cs:17-81), which:

  • detects an existing interest via IsInterestTargeting — again hard-codes participants.target == entity (InterestToggle.cs:94-111), and
  • creates a relationship of the fixed shape participants: { target: <entity>, user: <userId> }, where userId = entityBroker.EntityRepository.WorkspaceEntitySession.UserEntityId (the user entity, NOT the profile) (InterestToggle.cs:53-63).
  • deletion uses EntityChange { Data = null, EntityChangeMode = Replace } (InterestToggle.cs:33-49).
  • EntityBroker.ToggleInterestAsync (EntityBroker.cs:611-616) currently passes a bare interestTypeName string.

Relationship loading for views

MainWindowViewModel.WithInterestRelationships (MainWindowViewModel.cs:1377-1390) already augments a query's RelationshipsToReturn with all InterestCatalog.InterestTypeNames plus related:

internal static QueryRequest WithInterestRelationships(QueryRequest query, InterestCatalog? catalog)
{
    return query with
    {
        RelationshipsToReturn =
        [
            ..(query.RelationshipsToReturn ?? []),
            new GetRelationshipRequest { RelationshipTypeNames = new RelationshipTypeNameSet(["related"]) },
            ..(catalog is { InterestTypeNames.Count: > 0 } validCatalog
                ? [new GetRelationshipRequest { RelationshipTypeNames = new RelationshipTypeNameSet([.. validCatalog.InterestTypeNames]) }]
                : Array.Empty<GetRelationshipRequest>()),
        ],
    };
}

However, the view-definition-driven query builders do not route through it: TryReadSubViewGetRequest (MainWindowViewModel.cs:3661-3696, taking relationships-to-return at :3689), TryReadSubViewQueryRequest (:3699-3723, at :3718) and TryReadGetEntityRequest (:3725-3780, at :3774) take relationships-to-return verbatim from the view JSON. Because those paths bypass WithInterestRelationships, some views load only the relationship types the JSON names, so an interest badge (including the new default) may not reflect current state on every view.

The unit WithInterestRelationships (:1377) is applied to plain query sub-views at MainWindowViewModel.cs:1364, but the GetRequest/GetEntityRequest builders (TryReadSubViewGetRequest/TryReadGetEntityRequest) never reach that call site, so their generated requests omit the interest relationship types entirely.

Why a plain registration is insufficient

Merely adding "interest-type" to default-entity-type.json would make the badge appear, but:

  • applied-detection would never fire (IsTargetOf looks for participants.target, but default uses participants.value), and
  • toggling would create a malformed default relationship (it would write {target, user} instead of {value, applied-to}).

The generic machinery must therefore be made data-driven about (a) which participant references the badged entity, and (b) which participant(s) scope the interest and to which session identity they bind. Because these two attributes are made required, the existing interest definitions must also be updated to declare their {target, user} shape explicitly.

Affected Files

File Contribution
Phantom.Workspaces.Data.Core/JsonSchemas/interest-type.json Add required declarative participant-mapping properties: target-participant (participant naming the badged entity) and applies-to (array of scope-participant descriptors: participant-property-name, optional entity-types, and session-value). Add both to the top-level required list. No singleton property.
.../schema-definitions/actionable-entity-type.json Add target-participant: "target" and applies-to: [{ participant-property-name: "user", entity-types: ["user"], session-value: "user-entity-id" }] so the existing interest satisfies the now-required attributes.
.../schema-definitions/blocked-entity-type.json Same explicit {target, user} mapping as actionable.
.../schema-definitions/assigned-to-entity-type.json Same explicit {target, user} mapping.
.../schema-definitions/not-interesting-entity-type.json Same explicit {target, user} mapping.
.../schema-definitions/default-entity-type.json Add "interest-type" and "relationship-type" to entity-types; add applied/notApplied indicators; "display-entity-types": ["workspace"]; target-participant: "value"; applies-to: [{ participant-property-name: "applied-to", entity-types: ["user-computer-profile"], session-value: "user-computer-profile-entity-id" }]. Registration point for the new interest. No singleton.
Phantom.Workspaces/InterestCatalog.cs Extend InterestTypeDefinition with required TargetParticipant and AppliesTo (list of { ParticipantPropertyName, EntityTypes, SessionValue }); parse them in TryReadInterestType (:115-138). No Singleton field.
Phantom.Workspaces/InterestBadgeProjector.cs Replace hard-coded IsTargetOf (:137-149) with data-driven matching: applied when participants[TargetParticipant] == entity AND every AppliesTo participant equals the corresponding current-session identity. Project (:18-39) gains the session identities (user id + profile id).
Phantom.Workspaces/InterestToggle.cs Replace hard-coded {target, user} create/detect (:17-111) with data-driven logic: resolve AppliesTo participant values from the session; detect existing by TargetParticipant == entity AND matching AppliesTo; toggle-off deletes it; toggle-on writes TargetParticipant + AppliesTo participants. No singleton clearing. Accept an InterestTypeDefinition instead of a bare name.
Phantom.Workspaces/EntityBroker.cs ToggleInterestAsync (:611-616) resolves the InterestTypeDefinition from the catalog by name and passes it to InterestToggle.ToggleAsync.
Phantom.Workspaces/ViewModels/MainWindowViewModel.cs Route the view-definition query builders (TryReadSubViewGetRequest :3661, TryReadSubViewQueryRequest :3699, TryReadGetEntityRequest :3725) through the interest-relationship augmentation so every view always loads all interest relationship types (verified by the view-query tests requested in issue feedback). Pass the session identities into InterestBadgeProjector.Project at the projection sites (:1536, :1545).

Design / Fix

Chosen semantics

  • Per-profile, multi-profile: a default relationship is scoped to a user-computer-profile via its applied-to participant. Any profile may mark a workspace as its default independently; toggling for the current profile never touches another profile's default relationships.
  • No singleton: toggling default on creates a default relationship for the current profile; toggling off deletes it. The feature does not auto-clear a profile's other defaults — if a profile has more than one default, the existing startup read path (QueryDefaultWorkspaceIdsAsync) already returns them all. Any "only one default" policy is out of scope for this issue.
  • Required, explicit mapping: target-participant and applies-to are required on every interest-type. Existing interests declare their {target, user} shape explicitly (no code-side defaults). Behaviour is entirely determined by declared metadata — no code branches on the literal name default.

1. Generalise the interest-type schema (required participant mapping)

Extend interest-type.json with required properties (no singleton):

"target-participant": {              // participant naming the badged entity
  "$ref": "core.json#/$defs/local-string"
},
"applies-to": {                      // scope participants bound to a session identity
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "participant-property-name": { "$ref": "core.json#/$defs/local-string" },
      "entity-types": { "type": "array", "items": { "$ref": "core.json#/$defs/entity-type-id" } },
      "session-value": { "enum": ["user-entity-id", "user-computer-profile-entity-id"] }
    },
    "required": ["participant-property-name", "session-value"],
    "unevaluatedProperties": false
  }
}

Add target-participant and applies-to to the top-level required array (alongside entity-types, applied, notApplied). Because the attributes are required, there are no implicit code-side defaults — every interest definition must supply them.

2. Update the existing interests to declare {target, user} explicitly

Each of actionable-entity-type.json, blocked-entity-type.json, assigned-to-entity-type.json, not-interesting-entity-type.json gains:

"target-participant": "target",
"applies-to": [
  { "participant-property-name": "user", "entity-types": ["user"], "session-value": "user-entity-id" }
]

This reproduces today's behaviour (target entity + user == current user) as explicit data, satisfying the now-required schema.

3. Register default as an interest

Edit default-entity-type.json to also be an interest-type + relationship-type, scoped to workspaces, with the {value, applied-to} mapping:

"entity-types": [ "entity", "interest-type", "relationship-type", "entity-type", "json-schema", "note" ],
...
"applied":    { "indicator": "⭐ Default",     "description": "Default workspace",        "actionText": "Clear default workspace" },
"notApplied": { "indicator": "☆ Not Default", "description": "Not the default workspace", "actionText": "Make default workspace" },
"display-entity-types": ["workspace"],
"target-participant": "value",
"applies-to": [
  { "participant-property-name": "applied-to", "entity-types": ["user-computer-profile"], "session-value": "user-computer-profile-entity-id" }
]

display-entity-types: ["workspace"] makes ShouldShowBadge (InterestBadgeProjector.cs:41-97) show the badge only on workspace entities. InterestCatalog.CreateAsync discovers it automatically because it now carries interest-type, and the interest-relationship augmentation therefore includes default in every view's RelationshipsToReturn.

4. Extend InterestTypeDefinition + parsing (InterestCatalog.cs)

public sealed record InterestAppliesTo(
    string ParticipantPropertyName,
    IReadOnlySet<string>? EntityTypes,
    InterestSessionValue SessionValue);

public enum InterestSessionValue { UserEntityId, UserComputerProfileEntityId }

public sealed record InterestTypeDefinition(
    string Name,
    string AppliedGlyph, string NotAppliedGlyph,
    string AppliedDescription, string NotAppliedDescription,
    string AppliedActionText, string NotAppliedActionText,
    IReadOnlySet<string>? DisplayEntityTypes,
    string TargetParticipant,                    // required
    IReadOnlyList<InterestAppliesTo> AppliesTo);  // required

TryReadInterestType (InterestCatalog.cs:115-138) reads the required target-participant and applies-to[] (mapping session-value string → InterestSessionValue). A definition missing them is malformed data; treat consistently with existing parse-failure handling.

5. Data-driven applied-detection (InterestBadgeProjector.cs)

Project gains the current session identities; GetAppliedInterests/IsTargetOf are replaced by generic matching:

public static IReadOnlyList<BadgeModel> Project(
    InterestCatalog interestCatalog,
    EntityTypeCatalog entityTypeCatalog,
    EntitySnapshot entity,
    EntityId userId,
    EntityId userComputerProfileId) { ... }

private static bool IsAppliedTo(
    JsonElement rel, EntityId entityId, InterestTypeDefinition interest,
    EntityId userId, EntityId profileId)
{
    if (!TryParticipant(rel, interest.TargetParticipant, out var target)
        || !string.Equals(target, entityId.ToString(), StringComparison.OrdinalIgnoreCase))
        return false;

    foreach (var scope in interest.AppliesTo)
    {
        var expected = scope.SessionValue == InterestSessionValue.UserComputerProfileEntityId
            ? profileId.ToString() : userId.ToString();
        if (!TryParticipant(rel, scope.ParticipantPropertyName, out var actual)
            || !string.Equals(actual, expected, StringComparison.OrdinalIgnoreCase))
            return false;
    }
    return true;
}

This makes standard interests apply only when target == entity AND user == current user (tighter than today, which ignored user), and treats a default relationship as applied only when value == workspace AND applied-to == currentProfile — so another profile's default reads as inactive. The projection call sites (MainWindowViewModel.cs:1536, :1545) pass WorkspaceEntitySession.UserEntityId and .UserComputerProfileEntityId.

6. Data-driven toggle, no singleton (InterestToggle.cs)

public static async Task ToggleAsync(EntityBroker entityBroker, EntitySnapshot entity,
    InterestTypeDefinition interest, CancellationToken ct = default)
{
    var session = entityBroker.EntityRepository.WorkspaceEntitySession;
    EntityId Resolve(InterestSessionValue v) =>
        v == InterestSessionValue.UserComputerProfileEntityId
            ? session.UserComputerProfileEntityId : session.UserEntityId;

    // Existing relationship: target participant == entity AND all applies-to == session scope.
    var existing = entity.Relationships.FirstOrDefault(r =>
        r.Data is JsonElement d && MatchesTargetAndScope(d, entity.EntityId, interest, Resolve));

    if (existing is not null) { /* delete (Data=null, Replace) — toggle OFF; return */ }

    // Toggle ON: build participants from TargetParticipant + AppliesTo(session values), then create.
    var participants = new Dictionary<string, string> { [interest.TargetParticipant] = entity.EntityId.Value.ToString() };
    foreach (var s in interest.AppliesTo) participants[s.ParticipantPropertyName] = Resolve(s.SessionValue).Value.ToString();
    // entity-types: ["entity", interest.Name, "relationship"]; participants = participants; note = "Toggled from the entity badge."
}

Deletion reuses the existing EntityChange { Data = null, EntityChangeMode = Replace } pattern (InterestToggle.cs:33-49). There is no singleton clearing step — toggling a new default on does not remove a profile's other defaults. EntityBroker.ToggleInterestAsync (EntityBroker.cs:611-616) resolves the InterestTypeDefinition from the catalog by name and passes it in (instead of a bare string).

7. Centralise interest relationships on every view

Route the view-definition query builders through the same interest-relationship augmentation used by WithInterestRelationships (MainWindowViewModel.cs:1377) so all interest types (now including default) are always requested, merging with (not replacing) the JSON-declared relationships-to-return:

  • TryReadSubViewGetRequest (MainWindowViewModel.cs:3661-3696)
  • TryReadSubViewQueryRequest (:3699-3723)
  • TryReadGetEntityRequest (:3725-3780)

Each merges the JSON-declared relationships-to-return with the catalog's interest relationship types rather than using the JSON list verbatim, so every view can display and toggle every interest. WithInterestRelationships already merges related + all InterestCatalog.InterestTypeNames; the GetRequest/GetEntityRequest builders gain an equivalent merge (the current query-subview path only reaches WithInterestRelationships at :1364).

Test requirement (per issue feedback): the generated queries/requests produced from a View definition must be tested to ensure that all existing interests are retrieved — not merely the relationship types named in the view JSON. This means asserting that the request built by each of the three builders (and the merge helper WithInterestRelationships) requests every InterestCatalog.InterestTypeNames entry (actionable, blocked, assigned-to, not-interesting, and the new default) in addition to any JSON-declared relationships. See the view-query rows in Expected Tests.

Considered / Background (not chosen)

  • Optional attributes with implicit code-side defaults. A previous revision made target-participant/applies-to optional and had InterestCatalog synthesise the {target, user} shape when absent, leaving existing interest JSON untouched. Rejected per issue feedback ("set the attributes as required and update the current interests to provide those values"): the attributes are now required and every existing interest definition declares them explicitly.
  • singleton / singleton-per-scope semantics. A previous revision added a singleton attribute and had the toggle clear a profile's prior default before adding a new one. Rejected per issue feedback ("Let's not add 'singleton' behavior"): no singleton attribute, no auto-clear. Multiple defaults per profile are permitted; the startup read path already returns all of them.
  • Per-name special-casing in code (an if (interestTypeName == "default") branch that hard-codes the {value, applied-to} shape). Rejected: it does not scale to future participant-shaped interests and duplicates shape knowledge across InterestBadgeProjector and InterestToggle. The chosen design moves the shape into declarative interest-type metadata so one generic code path serves all interests.
  • Multi-default modeled as a plain boolean per workspace (no scope participant). Rejected: the read path is keyed on applied-to == profile, so scope must be a participant, not a bare flag.

Expected Tests

Model on InterestBadgeProjectorTests (xUnit [Fact], static InterestCatalog built from InterestTypeDefinition literals — e.g. new InterestTypeDefinition("actionable", "❗", "○", "Actionable", "Not actionable", "Mark actionable", "Clear actionable", null, ...)), InterestToggleTests ([AvaloniaFact], EntityBroker.CreateInitializedAsync, SeedAsync + GetWithInterestsAsync), SchemaPopulatorTests (xUnit [Fact]), and MainWindowIntegrationTests ([AvaloniaFact], profile via WorkspaceEntitySession.UserComputerProfileEntityId, SeedEntityAsync). Existing InterestTypeDefinition literals in InterestBadgeProjectorTests gain the new required positional args (TargetParticipant, AppliesTo) with the standard {target, user} values.

View-generated-query coverage (per issue feedback). The generated queries/requests produced from a View definition must be tested to ensure all existing interests are retrieved. Reuse the existing view-query test classes and their real style:

  • SubViewRelationshipMergeTests (xUnit [Fact]) — directly exercises MainWindowViewModel.WithInterestRelationships, building an InterestCatalog from InterestTypeDefinition literals (new([new InterestTypeDefinition("interest-a", "●", "○", "", "", "", "", null)])) and asserting the merged RelationshipsToReturn type-name set via result.RelationshipsToReturn!.Select(r => r.RelationshipTypeNames?.Values ?? []).SelectMany(v => v).ToHashSet().
  • TryReadSubViewQueryRequestTests (xUnit [Fact]) — invokes the private view builders by reflection (typeof(MainWindowViewModel).GetMethod("TryReadSubViewQueryRequest", BindingFlags.Static | BindingFlags.NonPublic)) on view-definition JSON, then asserts the generated request's RelationshipsToReturn after augmentation. Add sibling reflection helpers for TryReadSubViewGetRequest and TryReadGetEntityRequest.
  • InterestViewQueryTests ([AvaloniaFact]) — end-to-end: seeds an in-memory EntityRepository, drives a view definition, and asserts the executed query requested every interest relationship type.

New view-query tests must assert that every InterestCatalog.InterestTypeNames entry (actionable, blocked, assigned-to, not-interesting, default) appears in the generated request, and that JSON-declared relationships-to-return entries are preserved alongside them.

Test Name Class What It Verifies
Project_DefaultInterest_ShowsOnWorkspaceEntities InterestBadgeProjectorTests The default interest badge is projected for a workspace entity via display-entity-types.
Project_DefaultInterest_DoesNotShowOnNonWorkspaceEntities InterestBadgeProjectorTests The default badge is filtered out for non-workspace entity types (e.g. task).
Project_ConfiguredTargetParticipant_MarksActiveWhenValueMatchesEntity InterestBadgeProjectorTests A default relationship with value == workspace and applied-to == currentProfile projects as applied/active.
Project_AppliesToScope_MarksInactiveWhenAppliedToIsAnotherProfile InterestBadgeProjectorTests A default relationship whose applied-to is a different profile is not treated as applied.
Project_StandardInterest_MarksInactiveWhenUserParticipantIsAnotherUser InterestBadgeProjectorTests The now-explicit {target, user} applies-to: an interest whose user participant differs from the session user reads as inactive.
ToggleAsync_DefaultInterest_WhenNotDefault_CreatesRelationshipWithValueAndAppliedToParticipants InterestToggleTests Toggling on creates a default relationship { value: workspace, applied-to: currentProfile }.
ToggleAsync_DefaultInterest_WhenAlreadyDefault_RemovesRelationship InterestToggleTests Toggling off removes the workspace's default relationship for the current profile.
ToggleAsync_DefaultInterest_WhenAnotherWorkspaceIsDefaultForSameProfile_LeavesExistingDefaultIntact InterestToggleTests No singleton: toggling a second workspace default on does not remove the profile's other default relationship.
ToggleAsync_DefaultInterest_WhenDefaultExistsForAnotherProfile_LeavesOtherProfileDefaultIntact InterestToggleTests Multi-profile: toggling for the current profile does not remove another profile's default relationship.
ToggleAsync_DefaultInterest_AddsThenRemovesTheDefaultRelationship InterestToggleTests Round-trip on→off leaves no default relationship (mirrors ToggleAsync_AddsThenRemovesTheInterestRelationship).
ToggleAsync_StandardInterest_StillWritesTargetAndUserParticipants InterestToggleTests Regression: the generalised toggle preserves the {target, user} shape for the standard interests.
Populate_RegistersDefaultAsInterestTypeWithValueAppliedToMapping SchemaPopulatorTests The seeded default-entity-type validates as an interest-type and carries target-participant: "value" + applies-to bound to user-computer-profile.
Populate_ExistingInterestTypes_DeclareTargetAndUserAppliesTo SchemaPopulatorTests The seeded actionable/blocked/assigned-to/not-interesting interest types each declare the required target-participant: "target" and applies-to (useruser-entity-id).
InitializeAsync_AfterTogglingDefaultInterest_OpensDefaultWorkspace MainWindowIntegrationTests After toggling the default interest on for a workspace, startup opens that workspace (integrates with InitializeAsync_WithDefaultRelationship_OpensDefaultWorkspace at MainWindowIntegrationTests.cs:4700).
WithInterestRelationships_WithAllExistingInterests_RequestsEveryInterestRelationshipType SubViewRelationshipMergeTests Given a catalog containing all existing interests (actionable, blocked, assigned-to, not-interesting, default), the merged query's RelationshipsToReturn requests every one (plus related).
WithInterestRelationships_WithJsonDeclaredRelationships_PreservesThemAlongsideInterests SubViewRelationshipMergeTests JSON-declared relationships-to-return entries survive the merge and coexist with the added interest relationship types (including default).
TryReadSubViewQueryRequest_ForViewDefinition_GeneratedQueryRetrievesEveryInterestType TryReadSubViewQueryRequestTests A query built from a view definition is augmented so every interest relationship type — including default — is retrieved, not only those named in the view JSON.
TryReadSubViewGetRequest_ForViewDefinition_GeneratedGetRetrievesEveryInterestType TryReadSubViewQueryRequestTests A GetRequest built from a view definition is augmented so every interest relationship type (incl. default) is retrieved.
TryReadGetEntityRequest_ForViewDefinition_GeneratedGetEntityRetrievesEveryInterestType TryReadSubViewQueryRequestTests A GetEntityRequest built from a view definition retrieves every interest relationship type (incl. default) in addition to its JSON-declared relationships.
InboxView_GeneratedQuery_RetrievesAllInterestRelationshipTypesIncludingDefault InterestViewQueryTests End-to-end: the view-definition query the view model executes requests every interest relationship type (incl. default), so all interest badges reflect current state on that view.

Metadata

Metadata

Labels

bugSomething isn't workingdiagnosedRoot cause identifiedenhancementNew feature or requestqueuedIn the active work queue (tracked in work-queue.md)verified-locallyImplementation has been verified locally

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions