Skip to content

Commit 18df710

Browse files
julianknutsenclaude
andcommitted
Add 5 UX improvements: fast browse, dashboard, prefix matching, hints, filters
- Fast browse: pull upstream then query local by default (--ephemeral for old clone behavior) - Personal dashboard: wl me shows claimed items, awaiting review, recent completions - Smart ID prefix matching: wl claim w-ab resolves if unambiguous (all 9 ID commands) - Contextual next-step hints: dimmed "Next: ..." after post, claim, unclaim, done, accept, reject, close - Browse filter extensions: --posted-by, --claimed-by, --search flags Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d9e0467 commit 18df710

18 files changed

Lines changed: 346 additions & 55 deletions

cmd/wl/cmd_accept.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ func runAccept(cmd *cobra.Command, stdout, _ io.Writer, wantedID string, quality
8181
if err != nil {
8282
return fmt.Errorf("loading wasteland config: %w", err)
8383
}
84+
85+
wantedID, err = resolveWantedArg(wlCfg, wantedID)
86+
if err != nil {
87+
return err
88+
}
89+
8490
rigHandle := wlCfg.RigHandle
8591

8692
mc := newMutationContext(wlCfg, wantedID, noPush, stdout)
@@ -117,6 +123,8 @@ func runAccept(cmd *cobra.Command, stdout, _ io.Writer, wantedID string, quality
117123
"Push failed — changes saved locally. Run 'wl sync' to retry.")
118124
}
119125

126+
fmt.Fprintf(stdout, "\n %s\n", style.Dim.Render("Next: stamp issued. View: wl status "+wantedID))
127+
120128
return nil
121129
}
122130

cmd/wl/cmd_browse.go

Lines changed: 103 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ import (
1717

1818
func newBrowseCmd(stdout, stderr io.Writer) *cobra.Command {
1919
var (
20-
project string
21-
status string
22-
itemType string
23-
priority int
24-
limit int
25-
jsonOut bool
20+
project string
21+
status string
22+
itemType string
23+
priority int
24+
limit int
25+
jsonOut bool
26+
ephemeral bool
27+
postedBy string
28+
claimedBy string
29+
search string
2630
)
2731

2832
cmd := &cobra.Command{
@@ -31,9 +35,8 @@ func newBrowseCmd(stdout, stderr io.Writer) *cobra.Command {
3135
Args: cobra.NoArgs,
3236
Long: `Browse the Wasteland wanted board.
3337
34-
Clones the upstream commons database to a temporary directory, queries it,
35-
then deletes the clone. Works with all provider types (DoltHub, GitHub,
36-
file, git).
38+
Pulls the latest upstream changes into your local clone and queries it.
39+
Use --ephemeral to clone to a temp dir instead (slower, for edge cases).
3740
3841
EXAMPLES:
3942
wl browse # All open wanted items
@@ -42,9 +45,22 @@ EXAMPLES:
4245
wl browse --status claimed # Claimed items
4346
wl browse --priority 0 # Critical priority only
4447
wl browse --limit 5 # Show 5 items
45-
wl browse --json # JSON output`,
48+
wl browse --json # JSON output
49+
wl browse --posted-by alice # Items posted by alice
50+
wl browse --claimed-by bob # Items claimed by bob
51+
wl browse --search auth # Search in title
52+
wl browse --ephemeral # Clone upstream (slow)`,
4653
RunE: func(cmd *cobra.Command, _ []string) error {
47-
return runBrowse(cmd, stdout, stderr, project, status, itemType, priority, limit, jsonOut)
54+
return runBrowse(cmd, stdout, stderr, BrowseFilter{
55+
Status: status,
56+
Project: project,
57+
Type: itemType,
58+
Priority: priority,
59+
Limit: limit,
60+
PostedBy: postedBy,
61+
ClaimedBy: claimedBy,
62+
Search: search,
63+
}, jsonOut, ephemeral)
4864
},
4965
}
5066

@@ -54,6 +70,10 @@ EXAMPLES:
5470
cmd.Flags().IntVar(&priority, "priority", -1, "Filter by priority (0=critical, 2=medium, 4=backlog)")
5571
cmd.Flags().IntVar(&limit, "limit", 50, "Maximum items to display")
5672
cmd.Flags().BoolVar(&jsonOut, "json", false, "Output as JSON")
73+
cmd.Flags().BoolVar(&ephemeral, "ephemeral", false, "Clone upstream to temp dir instead of querying local (slow)")
74+
cmd.Flags().StringVar(&postedBy, "posted-by", "", "Filter by poster's rig handle")
75+
cmd.Flags().StringVar(&claimedBy, "claimed-by", "", "Filter by claimer's rig handle")
76+
cmd.Flags().StringVar(&search, "search", "", "Search in title")
5777
_ = cmd.RegisterFlagCompletionFunc("status", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
5878
return []string{"open", "claimed", "in_review", "completed", "withdrawn"}, cobra.ShellCompDirectiveNoFileComp
5979
})
@@ -64,21 +84,55 @@ EXAMPLES:
6484
return cmd
6585
}
6686

67-
func runBrowse(cmd *cobra.Command, stdout, _ io.Writer, project, status, itemType string, priority, limit int, jsonOut bool) error {
87+
func runBrowse(cmd *cobra.Command, stdout, _ io.Writer, filter BrowseFilter, jsonOut, ephemeral bool) error {
6888
cfg, err := resolveWasteland(cmd)
6989
if err != nil {
7090
return fmt.Errorf("loading wasteland config: %w", err)
7191
}
7292

73-
doltPath, err := exec.LookPath("dolt")
93+
if err := requireDolt(); err != nil {
94+
return err
95+
}
96+
97+
query := buildBrowseQuery(filter)
98+
99+
if ephemeral {
100+
return runBrowseEphemeral(stdout, cfg, query, jsonOut)
101+
}
102+
103+
return runBrowseLocal(stdout, cfg, query, jsonOut)
104+
}
105+
106+
func runBrowseLocal(stdout io.Writer, cfg *federation.Config, query string, jsonOut bool) error {
107+
fmt.Fprintf(stdout, "Syncing with upstream...\n")
108+
109+
if err := commons.PullUpstream(cfg.LocalDir); err != nil {
110+
return fmt.Errorf("pulling upstream: %w", err)
111+
}
112+
113+
if jsonOut {
114+
doltPath, _ := exec.LookPath("dolt")
115+
sqlCmd := exec.Command(doltPath, "sql", "-q", query, "-r", "json")
116+
sqlCmd.Dir = cfg.LocalDir
117+
sqlCmd.Stdout = stdout
118+
sqlCmd.Stderr = os.Stderr
119+
return sqlCmd.Run()
120+
}
121+
122+
csvData, err := commons.DoltSQLQuery(cfg.LocalDir, query)
74123
if err != nil {
75-
return fmt.Errorf("dolt not found in PATH — install from https://docs.dolthub.com/introduction/installation")
124+
return fmt.Errorf("querying local database: %w", err)
76125
}
77126

127+
return renderBrowseCSV(stdout, csvData)
128+
}
129+
130+
func runBrowseEphemeral(stdout io.Writer, cfg *federation.Config, query string, jsonOut bool) error {
131+
doltPath, _ := exec.LookPath("dolt")
132+
78133
_, commonsDB, _ := federation.ParseUpstream(cfg.Upstream)
79134
cloneURL := cfg.UpstreamURL
80135
if cloneURL == "" {
81-
// Backward compat: old configs without UpstreamURL.
82136
cloneURL = cfg.Upstream
83137
}
84138

@@ -99,14 +153,6 @@ func runBrowse(cmd *cobra.Command, stdout, _ io.Writer, project, status, itemTyp
99153
}
100154
fmt.Fprintf(stdout, "%s Cloned successfully\n\n", style.Bold.Render("✓"))
101155

102-
query := buildBrowseQuery(BrowseFilter{
103-
Status: status,
104-
Project: project,
105-
Type: itemType,
106-
Priority: priority,
107-
Limit: limit,
108-
})
109-
110156
if jsonOut {
111157
sqlCmd := exec.Command(doltPath, "sql", "-q", query, "-r", "json")
112158
sqlCmd.Dir = cloneDir
@@ -120,11 +166,14 @@ func runBrowse(cmd *cobra.Command, stdout, _ io.Writer, project, status, itemTyp
120166

121167
// BrowseFilter holds filter parameters for building a browse query.
122168
type BrowseFilter struct {
123-
Status string
124-
Project string
125-
Type string
126-
Priority int
127-
Limit int
169+
Status string
170+
Project string
171+
Type string
172+
Priority int
173+
Limit int
174+
PostedBy string
175+
ClaimedBy string
176+
Search string
128177
}
129178

130179
func buildBrowseQuery(f BrowseFilter) string {
@@ -142,6 +191,15 @@ func buildBrowseQuery(f BrowseFilter) string {
142191
if f.Priority >= 0 {
143192
conditions = append(conditions, fmt.Sprintf("priority = %d", f.Priority))
144193
}
194+
if f.PostedBy != "" {
195+
conditions = append(conditions, fmt.Sprintf("posted_by = '%s'", commons.EscapeSQL(f.PostedBy)))
196+
}
197+
if f.ClaimedBy != "" {
198+
conditions = append(conditions, fmt.Sprintf("claimed_by = '%s'", commons.EscapeSQL(f.ClaimedBy)))
199+
}
200+
if f.Search != "" {
201+
conditions = append(conditions, fmt.Sprintf("title LIKE '%%%s%%'", commons.EscapeSQL(f.Search)))
202+
}
145203

146204
query := "SELECT id, title, project, type, priority, posted_by, status, effort_level FROM wanted"
147205
if len(conditions) > 0 {
@@ -153,19 +211,8 @@ func buildBrowseQuery(f BrowseFilter) string {
153211
return query
154212
}
155213

156-
func renderBrowseTable(stdout io.Writer, doltPath, cloneDir, query string) error {
157-
sqlCmd := exec.Command(doltPath, "sql", "-q", query, "-r", "csv")
158-
sqlCmd.Dir = cloneDir
159-
output, err := sqlCmd.Output()
160-
if err != nil {
161-
var exitErr *exec.ExitError
162-
if errors.As(err, &exitErr) {
163-
return fmt.Errorf("query failed: %s", string(exitErr.Stderr))
164-
}
165-
return fmt.Errorf("running query: %w", err)
166-
}
167-
168-
rows := wlParseCSV(string(output))
214+
func renderBrowseCSV(stdout io.Writer, csvData string) error {
215+
rows := wlParseCSV(csvData)
169216
if len(rows) <= 1 {
170217
fmt.Fprintln(stdout, "No wanted items found matching your filters.")
171218
return nil
@@ -196,6 +243,21 @@ func renderBrowseTable(stdout io.Writer, doltPath, cloneDir, query string) error
196243
return nil
197244
}
198245

246+
func renderBrowseTable(stdout io.Writer, doltPath, cloneDir, query string) error {
247+
sqlCmd := exec.Command(doltPath, "sql", "-q", query, "-r", "csv")
248+
sqlCmd.Dir = cloneDir
249+
output, err := sqlCmd.Output()
250+
if err != nil {
251+
var exitErr *exec.ExitError
252+
if errors.As(err, &exitErr) {
253+
return fmt.Errorf("query failed: %s", string(exitErr.Stderr))
254+
}
255+
return fmt.Errorf("running query: %w", err)
256+
}
257+
258+
return renderBrowseCSV(stdout, string(output))
259+
}
260+
199261
func wlParseCSV(data string) [][]string {
200262
var rows [][]string
201263
for _, line := range strings.Split(strings.TrimSpace(data), "\n") {

cmd/wl/cmd_claim.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ func runClaim(cmd *cobra.Command, stdout, _ io.Writer, wantedID string, noPush b
4343
if err != nil {
4444
return fmt.Errorf("loading wasteland config: %w", err)
4545
}
46+
47+
wantedID, err = resolveWantedArg(wlCfg, wantedID)
48+
if err != nil {
49+
return err
50+
}
51+
4652
rigHandle := wlCfg.RigHandle
4753

4854
mc := newMutationContext(wlCfg, wantedID, noPush, stdout)
@@ -70,6 +76,8 @@ func runClaim(cmd *cobra.Command, stdout, _ io.Writer, wantedID string, noPush b
7076
"Push failed — changes saved locally. Run 'wl sync' to retry.")
7177
}
7278

79+
fmt.Fprintf(stdout, "\n %s\n", style.Dim.Render("Next: do the work, then: wl done "+wantedID+" --evidence <url>"))
80+
7381
return nil
7482
}
7583

cmd/wl/cmd_close.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ func runClose(cmd *cobra.Command, stdout, _ io.Writer, wantedID string, noPush b
4343
if err != nil {
4444
return fmt.Errorf("loading wasteland config: %w", err)
4545
}
46+
47+
wantedID, err = resolveWantedArg(wlCfg, wantedID)
48+
if err != nil {
49+
return err
50+
}
51+
4652
rigHandle := wlCfg.RigHandle
4753

4854
mc := newMutationContext(wlCfg, wantedID, noPush, stdout)
@@ -69,6 +75,8 @@ func runClose(cmd *cobra.Command, stdout, _ io.Writer, wantedID string, noPush b
6975
"Push failed — changes saved locally. Run 'wl sync' to retry.")
7076
}
7177

78+
fmt.Fprintf(stdout, "\n %s\n", style.Dim.Render("Next: item completed. View: wl status "+wantedID))
79+
7280
return nil
7381
}
7482

cmd/wl/cmd_delete.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ func runDelete(cmd *cobra.Command, stdout, _ io.Writer, wantedID string, noPush
4646
return fmt.Errorf("loading wasteland config: %w", err)
4747
}
4848

49+
wantedID, err = resolveWantedArg(wlCfg, wantedID)
50+
if err != nil {
51+
return err
52+
}
53+
4954
mc := newMutationContext(wlCfg, wantedID, noPush, stdout)
5055
cleanup, err := mc.Setup()
5156
if err != nil {

cmd/wl/cmd_done.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ func runDone(cmd *cobra.Command, stdout, _ io.Writer, wantedID, evidence string,
5454
if err != nil {
5555
return fmt.Errorf("loading wasteland config: %w", err)
5656
}
57+
58+
wantedID, err = resolveWantedArg(wlCfg, wantedID)
59+
if err != nil {
60+
return err
61+
}
62+
5763
rigHandle := wlCfg.RigHandle
5864

5965
mc := newMutationContext(wlCfg, wantedID, noPush, stdout)
@@ -84,6 +90,8 @@ func runDone(cmd *cobra.Command, stdout, _ io.Writer, wantedID, evidence string,
8490
"Push failed — changes saved locally. Run 'wl sync' to retry.")
8591
}
8692

93+
fmt.Fprintf(stdout, "\n %s\n", style.Dim.Render("Next: wait for review. Check: wl status "+wantedID))
94+
8795
return nil
8896
}
8997

0 commit comments

Comments
 (0)