feat: @-mention autocomplete; consultant bounty-board access; clickable atomization tasks
- @-mentions: typing "@" in task comments, the messages composer and the chat
widget now opens a user picker (new shared mention.js) that inserts "@Name ".
Backed by the existing /api/v1/users search; menus de-dupe per field.
- Consultants can now open the bounty board (read-only): nav link + page/API
access extended to consultants, board visibility resolves their assigned
customers, and cards show "Open" instead of claim actions for non-developers.
- Atomization board task titles (roots and subtasks) link to /tasks/{id} so
consultants can reach the detail view (comments, assignment, timeline).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+27
-10
@@ -19,7 +19,9 @@ import (
|
||||
|
||||
func (s *Server) routesBoard(mux *http.ServeMux) {
|
||||
dev := func(h http.HandlerFunc) http.Handler { return s.authedRole("developer", h) }
|
||||
mux.Handle("GET /api/v1/board", s.requireAuth(s.requireRole("developer", http.HandlerFunc(s.handleBoard))))
|
||||
// Developers and consultants can both read the board (consultants browse to
|
||||
// open task detail; claim/start/etc. below stay developer-only).
|
||||
mux.Handle("GET /api/v1/board", s.requireAuth(http.HandlerFunc(s.handleBoard)))
|
||||
mux.Handle("GET /api/v1/my-tasks", s.requireAuth(s.requireRole("developer", http.HandlerFunc(s.handleMyTasks))))
|
||||
mux.Handle("POST /api/v1/tasks/{id}/claim", dev(s.handleClaim))
|
||||
mux.Handle("POST /api/v1/tasks/{id}/claim/withdraw", dev(s.handleWithdrawClaim))
|
||||
@@ -39,17 +41,10 @@ func (s *Server) routesBoard(mux *http.ServeMux) {
|
||||
// assigned to a customer manage its tasks, so a task published by any of
|
||||
// them belongs to the same board.
|
||||
func (s *Server) boardCustomers(r *http.Request) ([]domain.Customer, error) {
|
||||
consultants, err := s.store.PoolConsultantIDs(r.Context(), CurrentUser(r.Context()).ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
me := CurrentUser(r.Context())
|
||||
seen := map[string]bool{}
|
||||
out := []domain.Customer{}
|
||||
for _, cid := range consultants {
|
||||
customers, err := s.store.ListCustomers(r.Context(), cid, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
add := func(customers []domain.Customer) {
|
||||
for _, c := range customers {
|
||||
if !seen[c.ID] {
|
||||
seen[c.ID] = true
|
||||
@@ -57,6 +52,28 @@ func (s *Server) boardCustomers(r *http.Request) ([]domain.Customer, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Developers: every customer that has a consultant who pooled them.
|
||||
if me.Roles.Developer {
|
||||
consultants, err := s.store.PoolConsultantIDs(r.Context(), me.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, cid := range consultants {
|
||||
customers, err := s.store.ListCustomers(r.Context(), cid, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
add(customers)
|
||||
}
|
||||
}
|
||||
// Consultants: every customer they are assigned to.
|
||||
if me.Roles.Consultant {
|
||||
customers, err := s.store.ListCustomers(r.Context(), me.ID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
add(customers)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user