Skip to content

add tags#14

Merged
devzeebo merged 2 commits into
mainfrom
feat/tags
Apr 10, 2026
Merged

add tags#14
devzeebo merged 2 commits into
mainfrom
feat/tags

Conversation

@devzeebo

@devzeebo devzeebo commented Apr 8, 2026

Copy link
Copy Markdown
Owner

No description provided.

@coderabbitai

coderabbitai Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9f7f5d6f-1323-41be-a5b3-9bae0059a523

📥 Commits

Reviewing files that changed from the base of the PR and between 1ba6607 and 8f8fa20.

📒 Files selected for processing (4)
  • cli/create_test.go
  • cli/update_test.go
  • domain/events_test.go
  • domain/handlers.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • domain/events_test.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Tags: create runes with repeatable --tag, view tags in rune details, filter list results by tag, and manage tags on existing runes with --add-tag/--remove-tag (supports replacing tags via update). Tags are normalized (trimmed, lowercased, empty entries removed) across CLI, API, server filtering, projections, and the UI.

Walkthrough

Adds tag support across the stack: CLI flags for create/list/update/show, domain command/event/tag handling, projector persistence, server-side tag filtering, and UI/client normalization and payload handling for tags.

Changes

Cohort / File(s) Summary
CLI
cli/create.go, cli/list.go, cli/show.go, cli/update.go
Added repeatable --tag, --add-tag, and --remove-tag flags; normalize tags (trim, lowercase, drop empties); send tags in requests or query params; show prints tags in human output.
CLI Tests
cli/create_test.go, cli/list_test.go, cli/show_test.go, cli/update_test.go
New tests and helpers validating parsing, normalization, and inclusion of tags, add_tags, and remove_tags in requests and outputs.
Domain Commands & Events
domain/commands.go, domain/events.go
Added Tags []string to Create/Created, and Tags *[]string, AddTags []string, RemoveTags []string to Update/Updated payloads (JSON tags with omitempty).
Domain Command/Event Tests
domain/commands_test.go, domain/events_test.go
Extended serialization/round-trip tests to include tags, add_tags, and remove_tags fields and their omitempty behavior.
Domain Handlers & Utilities
domain/handlers.go, domain/projectors/tag_utils.go
Rebuilt handler logic to emit/consume tag fields; added helpers normalizeTags, normalizeTagPointer, and applyTagMutations to normalize, dedupe, sort, replace/add/remove tags, and update RuneState.
Domain Handler Tests
domain/handlers_test.go
Subtests and helpers verifying state rebuild/tag mutation semantics and normalization.
Projectors & Projector Tests
domain/projectors/rune_detail.go, domain/projectors/rune_summary_projector.go, domain/projectors/rune_detail_test.go, domain/projectors/rune_summary_projector_test.go
Persist Tags []string in projections; initialize on create and apply applyTagMutations on updates. Tests added/updated to assert stored tags and mutation behavior.
Server Handlers & Tests
server/handlers.go, server/handlers_test.go
Parse tag/tags query values, normalize/dedupe, and filter ListRunes results by tag intersection; added handler test seeding tagged summaries and asserting filtered response.
UI Client & Types
ui/src/lib/api.ts, ui/src/lib/api.spec.ts, ui/src/types/rune.ts
Added tags?: string[] to RuneListItem; normalize incoming tags in list/detail responses; normalize and include tags in updateRune request payloads; tests updated accordingly.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant CLI
  participant API_Server as Server
  participant Domain
  participant EventStore
  participant Projector
  Note over User,CLI: Create rune with --tag flags
  User->>CLI: run create --tag Backend --tag " urgent "
  CLI->>API_Server: POST /create-rune { ..., tags:["backend","urgent"] }
  API_Server->>Domain: HandleCreateRune(cmd)
  Domain->>EventStore: persist RuneCreated{Tags: normalizeTags(cmd.Tags)}
  EventStore->>Projector: emit RuneCreated event
  Projector->>Projector: normalize/apply tags -> persist projection with Tags
  Projector-->>API_Server: projection persisted
  API_Server-->>CLI: 201 Created
  CLI-->>User: display success
Loading
sequenceDiagram
  autonumber
  participant User
  participant CLI
  participant API_Server as Server
  participant ProjectionDB as Projections
  Note over User,CLI: List runes filtered by tag
  User->>CLI: run list --tag backend
  CLI->>API_Server: GET /runes?tag=backend
  API_Server->>ProjectionDB: query summaries, filter by tags includes "backend"
  ProjectionDB-->>API_Server: filtered rune list
  API_Server-->>CLI: 200 OK [rune summaries with normalized tags]
  CLI-->>User: render list
Loading

Possibly related PRs

  • Feat/projection refactor #13: Modifies the same projection files (domain/projectors/rune_detail.go, domain/projectors/rune_summary_projector.go) and therefore likely overlaps with projection/tag persistence changes.
🚥 Pre-merge checks | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'add tags' is vague and overly generic—it lacks specificity about what aspect of tags is being added (creation, filtering, display, or update) and doesn't meaningfully distinguish this change from other tag-related work. Consider a more specific title such as 'Add tag support to create, list, update, and show commands' or 'Implement tag management across CLI and API layers' to better convey the scope and intent.
Description check ❓ Inconclusive No description was provided by the author, making it impossible to assess whether the description relates to the changeset. Add a pull request description that explains the changes made, such as which commands now support tags, how they are normalized, and what user-facing or API changes result from this work.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.4)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (5)
cli/list_test.go (1)

292-298: Consider adding a test for multiple tags.

The helper only supports a single tag. A test verifying multiple --tag flags are combined correctly (e.g., --tag Backend --tag urgenttags=backend,urgent) would strengthen coverage.

Example test case and helper extension
func (tc *listTestContext) execute_list_with_tags(tags ...string) {
	tc.t.Helper()
	cmd := NewListCmd(func() *Client { return tc.client }, tc.buf)
	args := []string{}
	for _, tag := range tags {
		args = append(args, "--tag", tag)
	}
	cmd.Command.SetArgs(args)
	cmd.Command.SetErr(tc.buf)
	tc.err = cmd.Command.Execute()
}
t.Run("passes multiple tag filters as comma-separated tags query parameter", func(t *testing.T) {
	tc := newListTestContext(t)

	// Given
	tc.server_that_captures_request_and_returns_runes()
	tc.client_configured()

	// When
	tc.execute_list_with_tags("Backend", "Urgent")

	// Then
	tc.command_has_no_error()
	tc.request_query_param_was("tags", "backend,urgent")
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/list_test.go` around lines 292 - 298, The current test helper
execute_list_with_tag in listTestContext only supports a single --tag flag; add
a variadic helper (e.g., execute_list_with_tags(tags ...string)) that builds
args by appending "--tag", tag for each provided tag and sets those on
NewListCmd (use the same NewListCmd(func() *Client { return tc.client }, tc.buf)
flow), then add a test that uses tc.execute_list_with_tags("Backend", "Urgent")
and asserts via the existing request capture helpers (e.g.,
request_query_param_was) that the server receives tags=backend,urgent
(lowercased, comma-separated).
cli/update.go (2)

49-70: Consider extracting shared tag normalization logic.

The same normalization pattern (trim, lowercase, filter empty) is repeated in create.go, list.go, and here. A shared helper would reduce duplication and ensure consistency.

Example helper function
// In a shared location like cli/tags.go
func normalizeTags(tags []string) []string {
	normalized := make([]string, 0, len(tags))
	for _, tag := range tags {
		tag = strings.ToLower(strings.TrimSpace(tag))
		if tag != "" {
			normalized = append(normalized, tag)
		}
	}
	return normalized
}

Usage in update.go:

 if cmd.Flags().Changed("add-tag") {
 	tags, _ := cmd.Flags().GetStringSlice("add-tag")
-	normalized := make([]string, 0, len(tags))
-	for _, tag := range tags {
-		tag = strings.ToLower(strings.TrimSpace(tag))
-		if tag != "" {
-			normalized = append(normalized, tag)
-		}
-	}
+	normalized := normalizeTags(tags)
 	body["add_tags"] = normalized
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/update.go` around lines 49 - 70, The tag normalization code repeated in
update.go (the blocks handling cmd.Flags().Changed("add-tag") and "remove-tag"
that produce body["add_tags"] and body["remove_tags"]) should be extracted into
a shared helper (e.g., normalizeTags(tags []string)) placed in a common CLI file
(like cli/tags.go) and used from create.go, list.go and update.go; replace the
inline loops by calling normalizeTags on the slice returned by
cmd.Flags().GetStringSlice and assign the result to body["add_tags"] /
body["remove_tags"] respectively to remove duplication and ensure consistent
behavior.

58-58: Minor: Empty arrays sent when all tags normalize to empty.

When --add-tag is provided but all values normalize to empty strings (e.g., --add-tag " "), an empty add_tags array is sent in the request body. This is benign since the server handles empty arrays gracefully, but it differs from how other flags work (they're only added to body when they have meaningful values).

Optional fix to skip empty arrays
 if cmd.Flags().Changed("add-tag") {
 	tags, _ := cmd.Flags().GetStringSlice("add-tag")
 	normalized := make([]string, 0, len(tags))
 	for _, tag := range tags {
 		tag = strings.ToLower(strings.TrimSpace(tag))
 		if tag != "" {
 			normalized = append(normalized, tag)
 		}
 	}
-	body["add_tags"] = normalized
+	if len(normalized) > 0 {
+		body["add_tags"] = normalized
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/update.go` at line 58, The code unconditionally sets body["add_tags"] =
normalized even when normalized is an empty slice; change the logic in the
update command (where variable normalized is computed and body is a map) to only
assign body["add_tags"] when len(normalized) > 0 so empty arrays are not
sent—locate the block that currently does body["add_tags"] = normalized and wrap
it with a conditional check on normalized's length.
ui/src/lib/api.ts (1)

145-150: Share one tag-normalization helper.

This trim/lowercase/filter pipeline is now copy-pasted across detail normalization, list normalization, and update payload construction. Pulling it into one private helper will keep those paths from drifting the next time tag rules change.

Refactor sketch
+  private normalizeTags(raw: unknown): string[] {
+    return Array.isArray(raw)
+      ? raw
+          .filter((tag): tag is string => typeof tag === "string")
+          .map((tag) => tag.trim().toLowerCase())
+          .filter((tag) => tag.length > 0)
+      : [];
+  }
+
   private normalizeRuneDetail(raw: RuneDetail | (Partial<RuneDetail> & { id: string })): RuneDetail {
@@
-      tags: Array.isArray(raw.tags)
-        ? raw.tags
-            .filter((tag): tag is string => typeof tag === "string")
-            .map((tag) => tag.trim().toLowerCase())
-            .filter((tag) => tag.length > 0)
-        : [],
+      tags: this.normalizeTags(raw.tags),
     };
   }
 
   private normalizeRuneListItem(raw: RuneListItem): RuneListItem {
     return {
       ...raw,
-      tags: Array.isArray(raw.tags)
-        ? raw.tags
-            .filter((tag): tag is string => typeof tag === "string")
-            .map((tag) => tag.trim().toLowerCase())
-            .filter((tag) => tag.length > 0)
-        : [],
+      tags: this.normalizeTags(raw.tags),
     };
   }
@@
     if (Array.isArray(updates.tags)) {
-      command.tags = updates.tags
-        .filter((tag): tag is string => typeof tag === "string")
-        .map((tag) => tag.trim().toLowerCase())
-        .filter((tag) => tag.length > 0);
+      command.tags = this.normalizeTags(updates.tags);
     }

Also applies to: 154-162, 342-347

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/lib/api.ts` around lines 145 - 150, Extract the repeated tag-cleaning
pipeline into a single private helper called normalizeTags(value: unknown):
string[] that returns an array of trimmed, lowercased, non-empty strings; then
replace each inline pipeline that begins with Array.isArray(raw.tags) (and
equivalent pipelines used in detail normalization, list normalization, and
update payload construction) with a call to normalizeTags(raw.tags) so all tag
normalization is centralized and consistent.
domain/projectors/tag_utils.go (1)

8-53: Centralize the tag normalization/mutation rules.

These helpers are already duplicated in domain/handlers.go, and server/handlers.go now has a third normalization path. If one copy changes independently, aggregate rebuilds, projections, and list filtering can drift on the same event stream.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@domain/projectors/tag_utils.go` around lines 8 - 53, The normalization and
mutation logic in normalizeTags and applyTagMutations is duplicated across
domain/projectors.go, domain/handlers.go, and server/handlers.go; extract these
functions into a single shared package (e.g., domain/tags or internal/tags) and
replace the local copies with imports and calls to the shared normalizeTags and
applyTagMutations to ensure one canonical implementation; update all callers
(functions that currently call normalizeTags or applyTagMutations in
domain/projectors, domain/handlers.go, and server/handlers.go) to import the new
package and remove the duplicated definitions so normalization/mutation rules
stay consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/create_test.go`:
- Around line 390-401: The helper request_body_has_array_field currently ignores
non-string elements and thus treats arrays like ["backend", 123] as valid;
change it to fail if any element is not a string: after asserting
tc.receivedBody[key] is a []any, iterate the slice in
request_body_has_array_field and if any item is not a string call tc.t.Fatalf or
require.Failf with a clear message including the offending index and value, then
collect the strings and assert equality with expected as before (use the
function name request_body_has_array_field and the receivedBody[key] reference
to locate the code).

In `@cli/update_test.go`:
- Around line 250-261: The helper request_body_has_array_field drops non-string
elements when building actual, allowing arrays with mixed types to pass; change
it to fail if any element is not a string by iterating over
tc.receivedBody[key].([]any) and asserting each item is a string (use
require.True/require.Fail with a clear message referencing
request_body_has_array_field and the offending item) so that mixed-type arrays
like ["backend", 123] cause the test to fail before comparing to expected.

In `@domain/events_test.go`:
- Line 382: The test currently only verifies marshal/unmarshal round-trips which
misses typos in JSON struct tags; update the test cases that populate tc.jsonMap
to explicitly assert the presence and exact keys "tags", "add_tags", and
"remove_tags" (and in the empty RuneCreated omission case ensure "tags" is
asserted as present/omitted correctly), replacing or augmenting the loose
type-based checks so the test fails if the JSON wire keys are incorrect; apply
the same explicit-key assertions for the other occurrences mentioned (the blocks
around the other test cases analogous to lines 407-409 and 685-688) and
reference the tc.jsonMap and RuneCreated test cases when making the changes.

In `@domain/handlers.go`:
- Around line 692-698: normalizeTagPointer currently collapses an explicit
empty-slice intent into nil because normalizeTags returns nil for empty input;
update normalizeTagPointer so that if tags != nil and len(*tags) == 0 it returns
a pointer to an explicit empty slice (e.g. &[]string{}), otherwise call
normalizeTags(*tags) and return a pointer to that result — this preserves the
difference between an explicit "clear all tags" (non-nil pointer to an empty
slice) and "omit field" (nil pointer). Reference: normalizeTagPointer and
normalizeTags.

---

Nitpick comments:
In `@cli/list_test.go`:
- Around line 292-298: The current test helper execute_list_with_tag in
listTestContext only supports a single --tag flag; add a variadic helper (e.g.,
execute_list_with_tags(tags ...string)) that builds args by appending "--tag",
tag for each provided tag and sets those on NewListCmd (use the same
NewListCmd(func() *Client { return tc.client }, tc.buf) flow), then add a test
that uses tc.execute_list_with_tags("Backend", "Urgent") and asserts via the
existing request capture helpers (e.g., request_query_param_was) that the server
receives tags=backend,urgent (lowercased, comma-separated).

In `@cli/update.go`:
- Around line 49-70: The tag normalization code repeated in update.go (the
blocks handling cmd.Flags().Changed("add-tag") and "remove-tag" that produce
body["add_tags"] and body["remove_tags"]) should be extracted into a shared
helper (e.g., normalizeTags(tags []string)) placed in a common CLI file (like
cli/tags.go) and used from create.go, list.go and update.go; replace the inline
loops by calling normalizeTags on the slice returned by
cmd.Flags().GetStringSlice and assign the result to body["add_tags"] /
body["remove_tags"] respectively to remove duplication and ensure consistent
behavior.
- Line 58: The code unconditionally sets body["add_tags"] = normalized even when
normalized is an empty slice; change the logic in the update command (where
variable normalized is computed and body is a map) to only assign
body["add_tags"] when len(normalized) > 0 so empty arrays are not sent—locate
the block that currently does body["add_tags"] = normalized and wrap it with a
conditional check on normalized's length.

In `@domain/projectors/tag_utils.go`:
- Around line 8-53: The normalization and mutation logic in normalizeTags and
applyTagMutations is duplicated across domain/projectors.go, domain/handlers.go,
and server/handlers.go; extract these functions into a single shared package
(e.g., domain/tags or internal/tags) and replace the local copies with imports
and calls to the shared normalizeTags and applyTagMutations to ensure one
canonical implementation; update all callers (functions that currently call
normalizeTags or applyTagMutations in domain/projectors, domain/handlers.go, and
server/handlers.go) to import the new package and remove the duplicated
definitions so normalization/mutation rules stay consistent.

In `@ui/src/lib/api.ts`:
- Around line 145-150: Extract the repeated tag-cleaning pipeline into a single
private helper called normalizeTags(value: unknown): string[] that returns an
array of trimmed, lowercased, non-empty strings; then replace each inline
pipeline that begins with Array.isArray(raw.tags) (and equivalent pipelines used
in detail normalization, list normalization, and update payload construction)
with a call to normalizeTags(raw.tags) so all tag normalization is centralized
and consistent.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d858deb4-1cfa-44c8-b7a8-e43e886d0a0f

📥 Commits

Reviewing files that changed from the base of the PR and between d76874d and 1ba6607.

📒 Files selected for processing (24)
  • cli/create.go
  • cli/create_test.go
  • cli/list.go
  • cli/list_test.go
  • cli/show.go
  • cli/show_test.go
  • cli/update.go
  • cli/update_test.go
  • domain/commands.go
  • domain/commands_test.go
  • domain/events.go
  • domain/events_test.go
  • domain/handlers.go
  • domain/handlers_test.go
  • domain/projectors/rune_detail.go
  • domain/projectors/rune_detail_test.go
  • domain/projectors/rune_summary_projector.go
  • domain/projectors/rune_summary_projector_test.go
  • domain/projectors/tag_utils.go
  • server/handlers.go
  • server/handlers_test.go
  • ui/src/lib/api.spec.ts
  • ui/src/lib/api.ts
  • ui/src/types/rune.ts

Comment thread cli/create_test.go
Comment thread cli/update_test.go
Comment thread domain/events_test.go
Comment thread domain/handlers.go
@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 4 file(s) based on 4 unresolved review comments.

Files modified:

  • cli/create_test.go
  • cli/update_test.go
  • domain/events_test.go
  • domain/handlers.go

Commit: 8f8fa2096bb89a99669907671f9b35aa14fe8f79

The changes have been pushed to the feat/tags branch.

Time taken: 4m 31s

Fixed 4 file(s) based on 4 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@devzeebo devzeebo merged commit 0b5479e into main Apr 10, 2026
3 checks passed
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.

1 participant