feat: formatted text, a list view, ticket details, public proposals and votes
Formatted text
- Descriptions, comments, replies and release notes render Markdown,
sanitised with DOMPurify (tested against scripts, handlers and
javascript: links). A pasted image is uploaded and written as
; the web fetches it with the reader's session,
so it works on private projects. Every text box has a preview. The
attachment upload now answers its attachmentId.
List view
- /p/:key/list: every ticket as rows, sortable by any column, filtered by
text, open/closed, type, priority and assignee; status, priority and
assignee change in place; bulk edit (the backlog's bar, now shared).
Ticket details
- Reactions on comments (GitHub's eight), for members and viewers.
- Duplicate a ticket: what it says, not where it stands.
- blockedBy: open tickets it waits for (BLOCKS / DEPENDS_ON), worked out
when reading with one query per board or page; shown on cards, list
rows and the ticket.
Public proposals
- A public project can take bug reports and ideas from anyone
(acceptsProposals): POST /api/explore/projects/{project}/proposals and
the form at /p/:key/proponer. They become tickets labelled «propuesta»,
opened by TaskStream; a honeypot field and 5 per address every 15
minutes keep spam out. No e-mail is asked: it would be public.
Votes
- Anyone signed in votes on a public project's tickets, members on a
private one (voteIssue, PUT/DELETE /api/issues/{issue}/vote): votes and
voted on each ticket, buttons on the ticket and the roadmap, a column
in the list. The showcase carries votes and the open proposals.
MCP: vote_issue, duplicate_issue, react_to_comment; listings show
blockers and votes. Docs, Word and PDF updated; chat notifications
(Discord, Slack, Telegram) noted for later.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,18 +5,18 @@ API repartida en `libs/web/*`.
|
||||
|
||||
## 4.1 Librerías
|
||||
|
||||
| Librería | Contenido |
|
||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `web/shared/infrastructure` | Cliente Apollo (`provideGraphql`: HTTP + `graphql-ws`), `API_CONFIG`, puerto `AccessTokenSource` |
|
||||
| `web/shared/ui` | Integración de `@pi/ui` (`providePiUi`), modo oscuro, iconos, `ToastService`, `DrawerService`, `AppShellComponent` |
|
||||
| `web/identity/domain` | `Session`, `CurrentUser`, `AuthConfig` y puertos `AuthGateway`, `ProfileGateway`, `SessionStorage`, `AuthorizationFlow` |
|
||||
| `web/identity/application` | `AuthFacade` (pública); por dentro `AuthStore` y eventos `authPageEvents`, `authApiEvents`, `sessionEvents` |
|
||||
| `web/identity/infrastructure` | `/api/auth/*` (HTTP), `me` (GraphQL), `localStorage`, PKCE con Web Crypto; enlaza `AccessTokenSource` con la `AuthFacade` |
|
||||
| `web/identity/presenters` | Página de login, callback de Pireon, `authGuard` / `guestGuard`, menú de usuario |
|
||||
| `web/tracker/domain` | Modelos, enums, workflow y puertos (`ProjectsGateway`, `IssuesGateway`, `TrackerRealtimeGateway`…) |
|
||||
| `web/tracker/application` | Facades `ProjectsFacade`, `ProjectFacade`, `BoardFacade`, `IssueFacade` y puerto `Notifications`; por dentro, stores y eventos |
|
||||
| `web/tracker/infrastructure` | Adaptadores GraphQL con operaciones tipadas (codegen) |
|
||||
| `web/tracker/presenters` | Páginas: proyectos, tablero, ticket, releases, seguimiento, actividad y miembros |
|
||||
| Librería | Contenido |
|
||||
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `web/shared/infrastructure` | Cliente Apollo (`provideGraphql`: HTTP + `graphql-ws`), `API_CONFIG`, puerto `AccessTokenSource` |
|
||||
| `web/shared/ui` | Integración de `@pi/ui` (`providePiUi`), modo oscuro, iconos, `ToastService`, `DrawerService`, `AppShellComponent` |
|
||||
| `web/identity/domain` | `Session`, `CurrentUser`, `AuthConfig` y puertos `AuthGateway`, `ProfileGateway`, `SessionStorage`, `AuthorizationFlow` |
|
||||
| `web/identity/application` | `AuthFacade` (pública); por dentro `AuthStore` y eventos `authPageEvents`, `authApiEvents`, `sessionEvents` |
|
||||
| `web/identity/infrastructure` | `/api/auth/*` (HTTP), `me` (GraphQL), `localStorage`, PKCE con Web Crypto; enlaza `AccessTokenSource` con la `AuthFacade` |
|
||||
| `web/identity/presenters` | Página de login, callback de Pireon, `authGuard` / `guestGuard`, menú de usuario |
|
||||
| `web/tracker/domain` | Modelos, enums, workflow y puertos (`ProjectsGateway`, `IssuesGateway`, `TrackerRealtimeGateway`…) |
|
||||
| `web/tracker/application` | Facades `ProjectsFacade`, `ProjectFacade`, `BoardFacade`, `IssueFacade` y puerto `Notifications`; por dentro, stores y eventos |
|
||||
| `web/tracker/infrastructure` | Adaptadores GraphQL con operaciones tipadas (codegen) |
|
||||
| `web/tracker/presenters` | Páginas: proyectos, tablero, lista, ticket, propuestas, releases, seguimiento, actividad y miembros; `ts-markdown` (texto con formato, saneado con DOMPurify), `ts-bulk-bar`, `ts-vote` |
|
||||
|
||||
## 4.2 Estado: NgRx Signals con eventos
|
||||
|
||||
@@ -109,15 +109,16 @@ Qué gana la página:
|
||||
- **Un sitio para la lógica de presentación**: abrir o refiltrar el tablero, buscar usuarios a partir de 2 caracteres…
|
||||
- **Tests sin plantilla**: la facade se prueba con gateways falsos (`facades.spec.ts`).
|
||||
|
||||
| Facade | Store | Ámbito | Responsabilidad |
|
||||
| ---------------- | --------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `AuthFacade` | `AuthStore` | raíz | Sesión, usuario, login (PKCE o email y contraseña de Pireon), renovación del token, restaurar al arrancar |
|
||||
| `ProjectsFacade` | `ProjectsStore` | raíz | Mis proyectos; se actualiza cuando me añaden o quitan (`myProjectsChanged`) |
|
||||
| `HomeFacade` | `HomeStore` | raíz | «Mi trabajo» en la portada: mis tickets abiertos por categoría (QA fallidos primero), esperando QA y próximas releases; se recarga al volver a la pestaña |
|
||||
| `ExploreFacade` | `ExploreStore` | raíz | Proyectos públicos («Explorar»), también sin sesión |
|
||||
| `ProjectFacade` | `ProjectStore` | ruta `/p/:key` | Proyecto, miembros, stats, releases, actividad en vivo, búsqueda de usuarios, configuración (`workflow`, `saveSettings`) y estadísticas (`loadAnalytics`, `loadBurnup`) |
|
||||
| `BoardFacade` | `BoardStore` | ruta `board` | Tablero kanban, filtros, movimiento optimista, nuevo ticket, cambios en vivo |
|
||||
| `IssueFacade` | `IssueStore` | ruta `issues/:issueKey` | Ticket, comentarios en vivo, historial y todas sus acciones |
|
||||
| Facade | Store | Ámbito | Responsabilidad |
|
||||
| ---------------- | --------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `AuthFacade` | `AuthStore` | raíz | Sesión, usuario, login (PKCE o email y contraseña de Pireon), renovación del token, restaurar al arrancar |
|
||||
| `ProjectsFacade` | `ProjectsStore` | raíz | Mis proyectos; se actualiza cuando me añaden o quitan (`myProjectsChanged`) |
|
||||
| `HomeFacade` | `HomeStore` | raíz | «Mi trabajo» en la portada: mis tickets abiertos por categoría (QA fallidos primero), esperando QA y próximas releases; se recarga al volver a la pestaña |
|
||||
| `ExploreFacade` | `ExploreStore` | raíz | Proyectos públicos («Explorar»), también sin sesión |
|
||||
| `ProjectFacade` | `ProjectStore` | ruta `/p/:key` | Proyecto, miembros, stats, releases, actividad en vivo, búsqueda de usuarios, configuración (`workflow`, `saveSettings`) y estadísticas (`loadAnalytics`, `loadBurnup`) |
|
||||
| `BoardFacade` | `BoardStore` | ruta `board` | Tablero kanban, filtros, movimiento optimista, nuevo ticket, cambios en vivo |
|
||||
| `ListFacade` | `ListStore` | ruta `list` | Todos los tickets del proyecto en filas: filtros (enviados a la API), cambios de estado, prioridad y persona en la fila, edición en bloque; se recarga tras cada cambio propio y con los de otros |
|
||||
| `IssueFacade` | `IssueStore` | ruta `issues/:issueKey` | Ticket, comentarios en vivo, historial y todas sus acciones |
|
||||
|
||||
Las de ámbito de ruta se registran con `provideProjectFacade()`, `provideBoardFacade()` o `provideIssueFacade()` en los
|
||||
`providers` del componente de la página (store + facade) y desaparecen al salir (con sus subscriptions). Las páginas
|
||||
@@ -168,7 +169,7 @@ La librería de componentes de Pireon se instala desde su Nexus (scope `@pi`, co
|
||||
| Drawers y modales se abren con `MatDialog` | `DrawerService.open(título, template)` con `getDrawerConfigs` de la librería |
|
||||
| `pi-search-field` es un buscador plegable (un icono) | Para un campo de búsqueda normal se usa `pi-text-field` |
|
||||
|
||||
| `button` globales: `min-width`, padding y radio con selectores muy específicos | Los botones propios de la app usan `.ts-icon-button`; `pi-action-button size="w100"` necesita `box-sizing` (en `styles.scss`) |
|
||||
| `button` globales: `min-width`, padding y radio con selectores muy específicos | Los botones propios de la app usan `.ts-icon-button`, o anulan esas reglas con `!important` (votos, reacciones); `pi-action-button size="w100"` necesita `box-sizing` (en `styles.scss`) |
|
||||
| Los campos pintan cualquier error que reciben, aunque no se hayan tocado | En el login, los errores se pasan solo tras salir del campo o intentar entrar (`(touch)`) |
|
||||
|
||||
Componentes en uso: `pi-action-button`, `pi-icon-button`, `pi-icon`, `pi-note`, `pi-text-field`, `pi-email-field`,
|
||||
|
||||
Reference in New Issue
Block a user