Jelajahi Sumber

Added labels to missing POM

Tim Jones 1 bulan lalu
induk
melakukan
8b929306f5

+ 1 - 1
Shared.Rcl/Components/ResourceTagEditor.razor

@@ -4,7 +4,7 @@
 @inject IAddTagUseCase<TResource> AddTagUseCase
 @inject IRemoveTagUseCase<TResource> RemoveTagUseCase
 
-<div class="md:col-span-2"
+<div class="md:col-span-2 "
      data-testid="@BaseTestId">
 
     <div class="flex items-center justify-between mb-1 group"

+ 1 - 1
Shared.Rcl/Desktops/DesktopCardComponent.razor

@@ -211,7 +211,6 @@
                              TestIdPrefix="desktop" />
 
 
-    </div>
 
     <div class="md:col-span-2" data-testid="desktop-notes-section">
         <div class="text-zinc-400 mb-1">Notes</div>
@@ -233,6 +232,7 @@
         }
     </div>
 </div>
+</div>
 
 <ConfirmModal IsOpen="_confirmDeleteOpen"
               IsOpenChanged="v => _confirmDeleteOpen = v"

+ 5 - 5
Shared.Rcl/Servers/ServerCardComponent.razor

@@ -229,13 +229,12 @@
             }
         </div>
 
+        <ResourceTagEditor Resource="Server"
+                           TestIdPrefix="server" />
 
-    </div>
-    <ResourceTagEditor Resource="Server"
-                       TestIdPrefix="server" />
+        <ResourceLabelEditor Resource="Server"
+                             TestIdPrefix="server" />
 
-    <ResourceLabelEditor Resource="Server"
-                         TestIdPrefix="server" />
 
 
     <div class="md:col-span-2">
@@ -260,6 +259,7 @@
 
         }
     </div>
+    </div>
 
 </div>
 

+ 1 - 0
Tests.E2e/PageObjectModels/AccessPointCardPom.cs

@@ -5,6 +5,7 @@ using Microsoft.Playwright;
 public class AccessPointCardPom(IPage page)
 {
     public TagsPom Tags => new(page);
+    public LabelsPom Labels => new(page);
 
     // Root
     public ILocator Card(string accessPointName)

+ 1 - 0
Tests.E2e/PageObjectModels/DesktopCardPom.cs

@@ -5,6 +5,7 @@ using Microsoft.Playwright;
 public class DesktopCardPom(IPage page)
 {    
     public TagsPom Tags => new(page);
+    public LabelsPom Labels => new(page);
 
     // -------------------------------------------------
     // Root + Navigation

+ 1 - 0
Tests.E2e/PageObjectModels/FirewallCardPom.cs

@@ -5,6 +5,7 @@ using Microsoft.Playwright;
 public class FirewallCardPom(IPage page)
 {
     public TagsPom Tags => new(page);
+    public LabelsPom Labels => new(page);
 
     // -------------------------------------------------
     // Dynamic Firewall Item (root)

+ 1 - 0
Tests.E2e/PageObjectModels/LaptopCardPom.cs

@@ -5,6 +5,7 @@ using Microsoft.Playwright;
 public class LaptopCardPom(IPage page)
 {
     public TagsPom Tags => new(page);
+    public LabelsPom Labels => new(page);
 
     // -------------------------------------------------
     // Root + Navigation

+ 1 - 0
Tests.E2e/PageObjectModels/RouterCardPom.cs

@@ -5,6 +5,7 @@ using Microsoft.Playwright;
 public class RouterCardPom(IPage page)
 {
     public TagsPom Tags => new(page);
+    public LabelsPom Labels => new(page);
 
     // -------------------------------------------------
     // Dynamic Router Item (root)

+ 1 - 0
Tests.E2e/PageObjectModels/ServiceCardPom.cs

@@ -5,6 +5,7 @@ namespace Tests.E2e.PageObjectModels;
 public class ServiceCardPom(IPage page)
 {
     public TagsPom Tags => new(page);
+    public LabelsPom Labels => new(page);
 
     // -------------------------------------------------
     // Root

+ 1 - 0
Tests.E2e/PageObjectModels/SwitchCardPom.cs

@@ -5,6 +5,7 @@ using Microsoft.Playwright;
 public class SwitchCardPom(IPage page)
 {
     public TagsPom Tags => new(page);
+    public LabelsPom Labels => new(page);
 
     // -------------------------------------------------
     // Dynamic Switch Item (root)

+ 1 - 0
Tests.E2e/PageObjectModels/SystemCardPom.cs

@@ -5,6 +5,7 @@ namespace Tests.E2e.PageObjectModels;
 public class SystemCardPom(IPage page)
 {
     public TagsPom Tags => new(page);
+    public LabelsPom Labels => new(page);
 
     // -------------------------------------------------
     // Helpers

+ 1 - 0
Tests.E2e/PageObjectModels/UpsCardPom.cs

@@ -5,6 +5,7 @@ namespace Tests.E2e.PageObjectModels;
 public class UpsCardPom(IPage page)
 {
     public TagsPom Tags => new(page);
+    public LabelsPom Labels => new(page);
 
     // -------------------------------------------------
     // Root

+ 61 - 0
Tests/EndToEnd/Labels/ServerWorkflowTests.cs

@@ -0,0 +1,61 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd.Labels;
+
+[Collection("Yaml CLI tests")]
+public class LabelsWorkflowTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string output, string yaml)> ExecuteAsync(params string[] args)
+    {
+        outputHelper.WriteLine($"rpk {string.Join(" ", args)}");
+
+        var output = await YamlCliTestHost.RunAsync(
+            args,
+            fs.Root,
+            outputHelper,
+            "config.yaml");
+
+        outputHelper.WriteLine(output);
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+  
+    [Theory]
+    [InlineData("servers")]
+    [InlineData("accesspoints")]
+    [InlineData("desktops")]
+    [InlineData("laptops")]
+    [InlineData("firewalls")]
+    [InlineData("routers")]
+    [InlineData("services")]
+    [InlineData("systems")]
+    [InlineData("ups")]
+    public async Task labels_cli_workflow_test(string resourceCommand)
+    {
+        await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), "");
+
+        // Create server
+        var (output, yaml) = await ExecuteAsync(resourceCommand, "add", "web-01");
+        Assert.Contains("web-01", yaml);
+
+        // Add label
+        (output, yaml) = await ExecuteAsync(resourceCommand, "label", "add", "web-01", "--key", "env", "--value", "production");
+        Assert.Contains("Label 'env' added", output);
+        Assert.Contains("env:", yaml);
+        Assert.Contains("production", yaml);
+
+        // Describe should show label
+        (output, _) = await ExecuteAsync(resourceCommand, "describe", "web-01");
+        Assert.Contains("env", output);
+        Assert.Contains("production", output);
+
+        // Remove label
+        (output, yaml) = await ExecuteAsync(resourceCommand, "label", "remove", "web-01", "--key", "env");
+        Assert.Contains("Label 'env' removed", output);
+        Assert.DoesNotContain("env:", yaml);
+    }
+}

+ 0 - 26
Tests/EndToEnd/ServerTests/ServerWorkflowTests.cs

@@ -138,30 +138,4 @@ public class ServerWorkflowTests(TempYamlCliFixture fs, ITestOutputHelper output
 
                      """, output);
     }
-    
-    [Fact]
-    public async Task servers_labels_cli_workflow_test()
-    {
-        await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), "");
-
-        // Create server
-        var (output, yaml) = await ExecuteAsync("servers", "add", "web-01");
-        Assert.Contains("web-01", yaml);
-
-        // Add label
-        (output, yaml) = await ExecuteAsync("servers", "label", "add", "web-01", "--key", "env", "--value", "production");
-        Assert.Contains("Label 'env' added", output);
-        Assert.Contains("env:", yaml);
-        Assert.Contains("production", yaml);
-
-        // Describe should show label
-        (output, _) = await ExecuteAsync("servers", "describe", "web-01");
-        Assert.Contains("env", output);
-        Assert.Contains("production", output);
-
-        // Remove label
-        (output, yaml) = await ExecuteAsync("servers", "label", "remove", "web-01", "--key", "env");
-        Assert.Contains("Label 'env' removed", output);
-        Assert.DoesNotContain("env:", yaml);
-    }
 }

+ 0 - 2
openspec/changes/archive/2026-02-24-add-key-value-labels/.openspec.yaml

@@ -1,2 +0,0 @@
-schema: spec-driven
-created: 2026-02-23

+ 0 - 90
openspec/changes/archive/2026-02-24-add-key-value-labels/design.md

@@ -1,90 +0,0 @@
-# Design: Add Key-Value Labels to Resources
-
-## Context
-
-RackPeek models IT infrastructure as a hierarchy of Resources (Hardware → System → Service). Resources currently have `Name`, `Tags` (string array), `Notes`, and `RunsOn`. Tags are single-value strings used for categorization (e.g., "production", "staging"); they are managed via `AddTagUseCase` / `RemoveTagUseCase` and displayed in `ResourceTagEditor`. Tags have no CLI commands today—they are Web UI only.
-
-Labels differ from Tags: they are key-value pairs (e.g., `env: production`, `owner: team-a`). Users need arbitrary metadata for organization, filtering, and reporting. The project overview and code-style rules already anticipate Labels (`Dictionary<string, string>` on `Resource`), but the implementation does not exist yet. `KeyValueModal` exists in Shared.Rcl and can be reused for label add/edit in the Web UI.
-
-## Goals / Non-Goals
-
-**Goals:**
-
-- Add `Labels` property (`Dictionary<string, string>`) to `Resource` base class.
-- Implement `AddLabelUseCase` and `RemoveLabelUseCase` following the Tags pattern.
-- Persist labels in YAML; existing resources without labels deserialize as empty dict.
-- Expose add/remove label commands in CLI for each resource branch (servers, switches, etc.).
-- Display and edit labels in the Web UI via a `ResourceLabelEditor` component (reusing `KeyValueModal`).
-
-**Non-Goals:**
-
-- Label-based filtering or querying in CLI/Web (future enhancement).
-- Label validation beyond key/value presence and length limits.
-- Bulk label operations or import/export.
-
-## Decisions
-
-### 1. Data model: `Dictionary<string, string>` on Resource
-
-**Decision:** Add `Labels` as `Dictionary<string, string>` on `Resource`, initialized to `new()` (never null).
-
-**Rationale:** Matches Kubernetes-style labels and the project's code-style rules. Simple, serializable, and supports arbitrary key-value pairs.
-
-**Alternatives considered:** `List<(string, string)>` — rejected because lookup by key is common and Dictionary is more ergonomic. `Dictionary<string, string?>` — rejected; empty string is acceptable for value.
-
-### 2. Use case pattern: Mirror Tags
-
-**Decision:** Create `AddLabelUseCase<T>` and `RemoveLabelUseCase<T>` in `UseCases/Labels/`, following the same structure as `AddTagUseCase` / `RemoveTagUseCase`.
-
-**Rationale:** Consistency with existing patterns; DI registration via open generics; single responsibility per use case.
-
-**Alternatives considered:** Single `LabelUseCase` with Add/Remove methods — rejected per SRP. Reusing Tag use cases with a "mode" — rejected; labels have different semantics (key-value vs single value).
-
-### 3. Normalization and validation
-
-**Decision:** Normalize key and value with `Trim()`. Validate: key and value non-empty, key length ≤ 50, value length ≤ 200. Add `ThrowIfInvalid.LabelKey` and `ThrowIfInvalid.LabelValue` (or similar) helpers.
-
-**Rationale:** Prevents whitespace-only labels; keeps YAML readable; avoids unbounded storage.
-
-**Alternatives considered:** No length limits — rejected for YAML size and UX. Lowercase keys — rejected; user may want case-sensitive keys (e.g., `Env` vs `env`).
-
-### 4. CLI structure: `label add` / `label remove` per resource branch
-
-**Decision:** Add a `label` sub-branch under each resource branch (servers, switches, systems, etc.) with `add` and `remove` commands. Example: `rpk servers label add web-01 --key env --value prod`, `rpk servers label remove web-01 --key env`.
-
-**Rationale:** Matches the E2E example workflow; consistent with `cpu add`, `drive add` sub-branch pattern.
-
-**Alternatives considered:** Global `label add <resource-type> <name> ...` — rejected; less discoverable. Positional args only — rejected; `--key`/`--value` improve clarity for key-value semantics.
-
-### 5. YAML serialization
-
-**Decision:** Rely on YamlDotNet default serialization for `Dictionary<string, string>`. No custom converter unless ordering or format requires it.
-
-**Rationale:** YamlDotNet serializes `Dictionary<string, string>` as YAML mapping. Existing resources without `Labels` will deserialize with default `new()`; migration not required if property is initialized.
-
-**Alternatives considered:** Custom converter for key ordering — deferred; can add later if needed. Schema version bump — only if migration logic is required; adding a new optional property typically does not require it.
-
-### 6. Web UI: ResourceLabelEditor component
-
-**Decision:** Create `ResourceLabelEditor` in Shared.Rcl, mirroring `ResourceTagEditor`. Use `KeyValueModal` for add/edit. Display labels as key-value chips with remove button; add button opens modal.
-
-**Rationale:** Reuses existing `KeyValueModal`; consistent UX with Tags; shared across all resource types via `@typeparam TResource`.
-
-## Risks / Trade-offs
-
-| Risk | Mitigation |
-|------|-------------|
-| YAML files from older app versions lack `Labels` | Initialize to empty dict in Resource; YamlDotNet will populate or leave default. Verify deserialization of legacy YAML. |
-| Duplicate label commands across 10+ resource branches | Use shared command base or factory; CliBootstrap will have repetitive but explicit registration (consistent with existing pattern). |
-| Key/value injection in YAML (e.g., special chars) | YamlDotNet handles escaping; validate key/value to reject control characters if needed. |
-| Labels bloat YAML file size | Length limits (50/200) mitigate; bulk operations out of scope. |
-
-## Migration Plan
-
-- **Deploy:** No schema version bump required if `Labels` is an additive, optional property. Existing YAML without `labels:` will deserialize with empty dictionary.
-- **Rollback:** Revert code; YAML with `labels:` will be ignored by older app (YamlDotNet typically ignores unknown properties). If strict compatibility is required, add migration step to strip labels when downgrading.
-
-## Open Questions
-
-- Should `describe` output format labels differently from tags (e.g., `Labels: env=prod, owner=team-a` vs `Tags: production, staging`)? **Recommendation:** Yes; use `key: value` format for labels.
-- Should we support label filtering in `list`/`get` in this change? **Recommendation:** No; keep scope to add/remove and display.

+ 0 - 29
openspec/changes/archive/2026-02-24-add-key-value-labels/proposal.md

@@ -1,29 +0,0 @@
-# Proposal: Add Key-Value Labels to Resources
-
-## Why
-
-The user would like the ability to add arbitrary labels to each resource. This helps the user associate key-value information on their resources (e.g., environment, owner, cost-center) for organization, filtering, and reporting.
-
-## What Changes
-
-- Addition of Labels (key-value section) to each resource
-- Add/Remove label use cases for managing labels via CLI and Web UI
-- YAML persistence of labels on resources
-- Display and editing of labels in the Web UI
-
-## Capabilities
-
-### New Capabilities
-
-- `resource-labels`: User can associate key-value attributes to their various resources in RackPeek. Labels are arbitrary string key-value pairs stored on each resource, with add/remove operations exposed via CLI and Web UI.
-
-### Modified Capabilities
-
-- None (no existing specs in `openspec/specs/`)
-
-## Impact
-
-- **Domain**: `Resource` base class gains `Labels` property (`Dictionary<string, string>`); new `AddLabelUseCase` and `RemoveLabelUseCase` in `UseCases/Labels/`
-- **Persistence**: YAML schema must serialize/deserialize labels; existing resources without labels remain valid (empty dict)
-- **CLI**: New commands for adding/removing labels per resource type (e.g., `rpk servers add-label <name> --key <key> --value <value>`, `rpk servers remove-label <name> --key <key>`)
-- **Web UI**: Resource card and edit flows must display and allow editing of labels (e.g., `ResourceLabelEditor` component)

+ 0 - 130
openspec/changes/archive/2026-02-24-add-key-value-labels/specs/resource-labels/spec.md

@@ -1,130 +0,0 @@
-# Spec: Resource Labels
-
-## ADDED Requirements
-
-### Requirement: Resource stores labels as key-value pairs
-
-Each resource SHALL have a Labels property that stores arbitrary string key-value pairs. Labels SHALL be initialized to an empty collection and MUST NOT be null.
-
-#### Scenario: New resource has empty labels
-
-- **WHEN** a resource is created
-- **THEN** the resource has an empty Labels collection
-
-#### Scenario: Labels are stored per resource
-
-- **WHEN** a label with key "env" and value "production" is added to resource "web-01"
-- **THEN** resource "web-01" has Labels containing "env" -> "production"
-- **AND** other resources are unaffected
-
-### Requirement: User can add a label to a resource
-
-The system SHALL allow users to add a label (key-value pair) to any resource via CLI and Web UI.
-
-#### Scenario: Add label via CLI
-
-- **WHEN** user runs `rpk servers label add web-01 --key env --value production`
-- **THEN** resource "web-01" has label "env" with value "production"
-- **AND** the change is persisted to YAML
-
-#### Scenario: Add label via Web UI
-
-- **WHEN** user opens the resource card, clicks add label, enters key "owner" and value "team-a", and submits
-- **THEN** the resource has label "owner" with value "team-a"
-- **AND** the change is persisted
-
-#### Scenario: Add label to existing key overwrites value
-
-- **WHEN** resource "web-01" has label "env" with value "staging"
-- **AND** user adds label "env" with value "production"
-- **THEN** resource "web-01" has label "env" with value "production"
-
-### Requirement: User can remove a label from a resource
-
-The system SHALL allow users to remove a label by key from any resource via CLI and Web UI.
-
-#### Scenario: Remove label via CLI
-
-- **WHEN** resource "web-01" has label "env" with value "production"
-- **AND** user runs `rpk servers label remove web-01 --key env`
-- **THEN** resource "web-01" no longer has label "env"
-- **AND** the change is persisted to YAML
-
-#### Scenario: Remove label via Web UI
-
-- **WHEN** resource has label "owner" with value "team-a"
-- **AND** user clicks remove on that label in the resource card
-- **THEN** the resource no longer has label "owner"
-- **AND** the change is persisted
-
-#### Scenario: Remove nonexistent key is no-op
-
-- **WHEN** resource "web-01" does not have label "env"
-- **AND** user runs `rpk servers label remove web-01 --key env`
-- **THEN** no error occurs
-- **AND** the resource is unchanged
-
-### Requirement: Labels persist in YAML
-
-Labels SHALL be serialized and deserialized from the YAML resource file. Existing resources without a labels section SHALL deserialize with an empty Labels collection.
-
-#### Scenario: Labels are written to YAML
-
-- **WHEN** resource "web-01" has labels "env: production" and "owner: team-a"
-- **AND** the resource is saved
-- **THEN** the YAML file contains a labels section for that resource with the key-value pairs
-
-#### Scenario: Legacy YAML without labels deserializes correctly
-
-- **WHEN** resource in YAML has no labels section
-- **THEN** the resource deserializes with an empty Labels collection
-
-### Requirement: Labels are displayed in resource views
-
-The system SHALL display labels in resource describe output (CLI) and resource cards (Web UI).
-
-#### Scenario: Describe shows labels
-
-- **WHEN** resource "web-01" has labels "env: production" and "owner: team-a"
-- **AND** user runs `rpk servers describe web-01`
-- **THEN** the output includes the labels in key-value format
-
-#### Scenario: Resource card shows labels
-
-- **WHEN** resource has label "env" with value "production"
-- **AND** user views the resource card in Web UI
-- **THEN** the label is displayed
-
-### Requirement: Label key and value are validated
-
-The system SHALL validate label key and value before adding. Key and value MUST be non-empty after trimming. Key length MUST NOT exceed 50 characters. Value length MUST NOT exceed 200 characters.
-
-#### Scenario: Empty key is rejected
-
-- **WHEN** user attempts to add a label with empty or whitespace-only key
-- **THEN** a validation error is returned
-- **AND** the resource is not updated
-
-#### Scenario: Empty value is rejected
-
-- **WHEN** user attempts to add a label with empty or whitespace-only value
-- **THEN** a validation error is returned
-- **AND** the resource is not updated
-
-#### Scenario: Key exceeds length limit is rejected
-
-- **WHEN** user attempts to add a label with key longer than 50 characters
-- **THEN** a validation error is returned
-- **AND** the resource is not updated
-
-#### Scenario: Value exceeds length limit is rejected
-
-- **WHEN** user attempts to add a label with value longer than 200 characters
-- **THEN** a validation error is returned
-- **AND** the resource is not updated
-
-#### Scenario: Add label for nonexistent resource fails
-
-- **WHEN** user attempts to add a label to a resource that does not exist
-- **THEN** a not-found error is returned
-- **AND** no change is persisted

+ 0 - 44
openspec/changes/archive/2026-02-24-add-key-value-labels/tasks.md

@@ -1,44 +0,0 @@
-# Tasks: Add Key-Value Labels to Resources
-
-## 1. Domain Model and Validation
-
-- [x] 1.1 Add `Labels` property (`Dictionary<string, string>`) to `Resource` base class, initialized to `new()`
-- [x] 1.2 Add `Normalize.LabelKey` and `Normalize.LabelValue` helpers (trim)
-- [x] 1.3 Add `ThrowIfInvalid.LabelKey` and `ThrowIfInvalid.LabelValue` (non-empty, key ≤ 50 chars, value ≤ 200 chars)
-- [x] 1.4 Create `AddLabelUseCase<T>` and `IAddLabelUseCase<T>` in `UseCases/Labels/`
-- [x] 1.5 Create `RemoveLabelUseCase<T>` and `IRemoveLabelUseCase<T>` in `UseCases/Labels/`
-- [x] 1.6 Register `IAddLabelUseCase<>` and `IRemoveLabelUseCase<>` in `ServiceCollectionExtensions`
-
-## 2. Persistence
-
-- [x] 2.1 Verify YamlDotNet serializes/deserializes `Dictionary<string, string>` for labels (no custom converter)
-- [x] 2.2 Add unit test for YAML round-trip with labels and legacy YAML without labels
-
-## 3. CLI Commands
-
-- [x] 3.1 Create `ServerLabelAddCommand` and `ServerLabelRemoveCommand` with `--key` and `--value` options
-- [x] 3.2 Add `label` branch with `add` and `remove` commands to servers in CliBootstrap
-- [x] 3.3 Add label add/remove commands for switches, routers, firewalls
-- [x] 3.4 Add label add/remove commands for systems, accesspoints, ups
-- [x] 3.5 Add label add/remove commands for desktops, laptops, services
-- [x] 3.6 Update `ServerDescribeCommand` to display labels in key-value format
-- [x] 3.7 Update describe commands for switches, routers, firewalls, systems, accesspoints, ups, desktops, laptops, services to display labels
-
-## 4. Web UI
-
-- [x] 4.1 Create `ResourceLabelEditor` component in Shared.Rcl (mirror `ResourceTagEditor`, use `KeyValueModal`)
-- [x] 4.2 Add `ResourceLabelEditor` to ServerCardComponent
-- [x] 4.3 Add `ResourceLabelEditor` to SwitchCardComponent, RouterCardComponent, FirewallCardComponent
-- [x] 4.4 Add `ResourceLabelEditor` to SystemCardComponent, AccessPointCardComponent, UpsCardComponent
-- [x] 4.5 Add `ResourceLabelEditor` to DesktopCardComponent, LaptopCardComponent, ServiceCardComponent
-
-## 5. Unit Tests
-
-- [x] 5.1 Add `AddLabelUseCaseTests` (new label, overwrite existing key, nonexistent resource)
-- [x] 5.2 Add `RemoveLabelUseCaseTests` (remove existing, remove nonexistent key no-op)
-- [x] 5.3 Add validation tests for `ThrowIfInvalid.LabelKey` and `ThrowIfInvalid.LabelValue`
-
-## 6. E2E Tests
-
-- [x] 6.1 Add CLI workflow test: add label, verify YAML, describe shows labels, remove label
-- [x] 6.2 Add browser E2E test for label add/remove in resource card

+ 0 - 132
openspec/specs/resource-labels/spec.md

@@ -1,132 +0,0 @@
-# resource-labels Specification
-
-## Purpose
-TBD - created by archiving change add-key-value-labels. Update Purpose after archive.
-## Requirements
-### Requirement: Resource stores labels as key-value pairs
-
-Each resource SHALL have a Labels property that stores arbitrary string key-value pairs. Labels SHALL be initialized to an empty collection and MUST NOT be null.
-
-#### Scenario: New resource has empty labels
-
-- **WHEN** a resource is created
-- **THEN** the resource has an empty Labels collection
-
-#### Scenario: Labels are stored per resource
-
-- **WHEN** a label with key "env" and value "production" is added to resource "web-01"
-- **THEN** resource "web-01" has Labels containing "env" -> "production"
-- **AND** other resources are unaffected
-
-### Requirement: User can add a label to a resource
-
-The system SHALL allow users to add a label (key-value pair) to any resource via CLI and Web UI.
-
-#### Scenario: Add label via CLI
-
-- **WHEN** user runs `rpk servers label add web-01 --key env --value production`
-- **THEN** resource "web-01" has label "env" with value "production"
-- **AND** the change is persisted to YAML
-
-#### Scenario: Add label via Web UI
-
-- **WHEN** user opens the resource card, clicks add label, enters key "owner" and value "team-a", and submits
-- **THEN** the resource has label "owner" with value "team-a"
-- **AND** the change is persisted
-
-#### Scenario: Add label to existing key overwrites value
-
-- **WHEN** resource "web-01" has label "env" with value "staging"
-- **AND** user adds label "env" with value "production"
-- **THEN** resource "web-01" has label "env" with value "production"
-
-### Requirement: User can remove a label from a resource
-
-The system SHALL allow users to remove a label by key from any resource via CLI and Web UI.
-
-#### Scenario: Remove label via CLI
-
-- **WHEN** resource "web-01" has label "env" with value "production"
-- **AND** user runs `rpk servers label remove web-01 --key env`
-- **THEN** resource "web-01" no longer has label "env"
-- **AND** the change is persisted to YAML
-
-#### Scenario: Remove label via Web UI
-
-- **WHEN** resource has label "owner" with value "team-a"
-- **AND** user clicks remove on that label in the resource card
-- **THEN** the resource no longer has label "owner"
-- **AND** the change is persisted
-
-#### Scenario: Remove nonexistent key is no-op
-
-- **WHEN** resource "web-01" does not have label "env"
-- **AND** user runs `rpk servers label remove web-01 --key env`
-- **THEN** no error occurs
-- **AND** the resource is unchanged
-
-### Requirement: Labels persist in YAML
-
-Labels SHALL be serialized and deserialized from the YAML resource file. Existing resources without a labels section SHALL deserialize with an empty Labels collection.
-
-#### Scenario: Labels are written to YAML
-
-- **WHEN** resource "web-01" has labels "env: production" and "owner: team-a"
-- **AND** the resource is saved
-- **THEN** the YAML file contains a labels section for that resource with the key-value pairs
-
-#### Scenario: Legacy YAML without labels deserializes correctly
-
-- **WHEN** resource in YAML has no labels section
-- **THEN** the resource deserializes with an empty Labels collection
-
-### Requirement: Labels are displayed in resource views
-
-The system SHALL display labels in resource describe output (CLI) and resource cards (Web UI).
-
-#### Scenario: Describe shows labels
-
-- **WHEN** resource "web-01" has labels "env: production" and "owner: team-a"
-- **AND** user runs `rpk servers describe web-01`
-- **THEN** the output includes the labels in key-value format
-
-#### Scenario: Resource card shows labels
-
-- **WHEN** resource has label "env" with value "production"
-- **AND** user views the resource card in Web UI
-- **THEN** the label is displayed
-
-### Requirement: Label key and value are validated
-
-The system SHALL validate label key and value before adding. Key and value MUST be non-empty after trimming. Key length MUST NOT exceed 50 characters. Value length MUST NOT exceed 200 characters.
-
-#### Scenario: Empty key is rejected
-
-- **WHEN** user attempts to add a label with empty or whitespace-only key
-- **THEN** a validation error is returned
-- **AND** the resource is not updated
-
-#### Scenario: Empty value is rejected
-
-- **WHEN** user attempts to add a label with empty or whitespace-only value
-- **THEN** a validation error is returned
-- **AND** the resource is not updated
-
-#### Scenario: Key exceeds length limit is rejected
-
-- **WHEN** user attempts to add a label with key longer than 50 characters
-- **THEN** a validation error is returned
-- **AND** the resource is not updated
-
-#### Scenario: Value exceeds length limit is rejected
-
-- **WHEN** user attempts to add a label with value longer than 200 characters
-- **THEN** a validation error is returned
-- **AND** the resource is not updated
-
-#### Scenario: Add label for nonexistent resource fails
-
-- **WHEN** user attempts to add a label to a resource that does not exist
-- **THEN** a not-found error is returned
-- **AND** no change is persisted
-