WIP: Publication admin / manage area#2271
Draft
wreality wants to merge 157 commits into
Draft
Conversation
- Migrate from typescript/typescript-operations plugins to client-preset - Enable colocated GraphQL fragments and queries in Vue SFCs - Add document scanning for .vue files in pages, layouts, and components - Update schema snapshot
- Define avatarImage fragment on User in AvatarImage.vue - Define avatarBlock fragment on User in AvatarBlock.vue - Use generated fragment types for component props
- Add PublicationAssignment and SubmissionAssignment models with builders - Add PublicationBuilder and SubmissionBuilder with search/filter scopes - Add Paginator interface listener for GraphQL pagination - Update User, Publication, and Submission models with new relationships - Add GraphQL types for publication/submission assignments with ordering - Add backend tests for user assignments, publications, and paginator - Update publication seeders with visibility/accepting fields
- Add QueryTable component wrapping Quasar q-table with GraphQL pagination - Add usePaginatedQuery composable for paginated GraphQL queries - Add useQueryCapabilities for introspecting query search/sort support - Add useUrlPaginationSync for syncing table state with URL params - Add DateTimeCell, NameAvatarCell, and WithAsideCell table cell components - Add FieldDisplay molecule for labeled field rendering - Add tests for useQueryCapabilities and useUrlPaginationSync
- Update client schema with new admin table types and pagination - Update codegen config and GraphQL queries - Update vue-apollo boot configuration - Update yarn.lock with new dependencies
- Add UsersIndex page with searchable, sortable QueryTable - Add UserDetailLayout with user info card, breadcrumbs, and tabs - Add UserDetails page showing user's publication assignments - Add row-click navigation and no-data empty states - Add tests for UsersIndex and UserDetails pages
- Add UserDetailsSubmissions page with sortable submissions table - Add filter panels for status, role, and publication filtering - Add row-click navigation to submission details - Add updated_at column with backend sorting support - Add no-data empty state message
- Add search, sorting, and filter support to publications table - Add PublicationsFilterPanel for visibility and accepting status filters - Add row-click navigation and router-link on publication names - Replace icon-only actions with configure button - Restyle create publication dialog with accent header - Move submit button from inline form to dialog actions - Expose CreateForm submit method via defineExpose - Update PublicationIndexPage and CreateForm tests
- Add admin dashboard page with card links to users and publications - Add /admin route and breadcrumbs on all admin pages - Simplify app header admin menu to single dashboard link - Add admin routes for dashboard, users, and user details - Update i18n with admin table headers, labels, and empty states - Add AdminDashboard test covering mount and card navigation
Change admin index "All Users" label to "Users" and delete unused UserListBasic and UserListBasicItem components.
…me mutator Remove responsive grid check and unused useQuasar import from UserDetailsSubmissions. Guard against null in Publication setNameAttribute to fix backend test failures.
…ma types Update PublicationTest to use `public` instead of removed `is_publicly_visible` query argument. Update SubmissionTest to use SubmissionAssignment type for user submissions. Add defaultCount to user submissions and publications paginators.
# Conflicts: # backend/app/Models/Publication.php # backend/app/Models/Submission.php # client/yarn.lock
Add yarn.lock entries for @graphql-codegen/client-preset and @graphql-codegen/fragment-matcher. Fix AdminDashboard test to use the local installQuasarPlugin helper instead of the removed @quasar/quasar-app-extension-testing-unit-vitest package.
Add use statement for Submission model in SubmissionAssignmentBuilder and fix arrow function formatting in Publication model.
Backport improvements from feature/publication-admin to keep QueryTable in sync across branches: - Toolbar: always-flat refresh button, inset separator before top-after slot content, and aligned search-hint caption - Row-click: forward via v-on object binding so Quasar only attaches a pointer cursor when the parent actually listens for @row-click - New column-level options (linkTo for router-link cells; cell/grid refinements) and additional QueryTable props (binary-state-sort, visibleColumns, grid, searchHint) - Expose pagination and result to parents via defineExpose NameAvatarCell now reads the user from scope.value (aligned with the dashboard cell convention), handles a null user with an em-dash placeholder, and shows username as a caption under the display name. UsersIndex passes the full row as field and sets hideUsername on the name column since username has its own dedicated column.
Add a publication-level dashboard page for editors and publication admins to view and filter submissions by status. Includes summary cards grouped by workflow category (needs action, in progress, awaiting author, completed) with status counts from a global publication query, and a paginated submissions table powered by a custom SubmissionPaginator that supports filter-aware statusCounts. Backend: - Add custom PaginatePublicationSubmissions resolver for publication- scoped paginated submissions (no user-visibility filter) - Add custom PaginateSubmissions resolver for top-level submissions query with statusCounts on the SubmissionPaginator type - Add SubmissionStatusCount type and submission_status_counts field on Publication for global (unfiltered) counts - Replace Publication.submissions @hasmany with paginated @paginate - Add SubmissionPaginator tests covering status counts, filters, pagination, ordering, and visibility scoping Frontend: - Add PublicationDashboard page at /publication/:id/dashboard - Add StatusBadgeCell with category-matched colors, icons, and color-blind accessible patterns (diagonal, zigzag, dots, crosshatch) - Add NameAvatarListCell for compact multi-user avatar display - Refactor NameAvatarCell to read from scope.value for flexibility - Add accessibility patterns gated behind .a11y-patterns CSS class, auto-enabled via prefers-contrast: more media query - Add dashboard button on PublicationHomePage for admins and editors
- NameAvatarListCell: add toggle to expand compact avatars into a list of full avatar+name rows; announce count to screen readers via Quasar's sr-only class and semantic ul/li markup - NameAvatarCell: show em-dash with aria-label for empty/null users - PublicationDashboard: select id on nested publication query so Apollo can normalize it with the cache entity from the global query, resolving the "Cache data may be lost" warning on navigation
- Use q-checkbox with label for the Select all/Deselect all row, leveraging native tri-state support via null model value - Bump category name to 1rem (16px) weight-medium to match the table's column header size - Add vertical separator between icon and category name, styled to inherit current text color so it reads correctly on both dark-text (warning/info) and white-text (secondary/completed) cards - Restructure individual status rows to mirror the select-all row: q-checkbox with label (col) + fixed-width count aligned with the three-dot menu in the header
…avatars - QueryTable: add binary-state-sort so header clicks cycle only between ascending and descending, never back to an unsorted state - NameAvatarListCell: single-user cells render the full avatar+name row (matching NameAvatarCell) instead of a lone collapsed avatar - NameAvatarListCell: expose NameAvatarListExpandAllKey injection key; each cell watches a parent-provided ref to sync expand/collapse state - NameAvatarCell/NameAvatarListCell: use size=md consistently so the avatar is the same size across single cell, collapsed, and expanded states - NameAvatarListCell: flush the expand/collapse button to the right with a q-space spacer; drop ul/li wrapper in favor of rendering q-item directly inside q-td to match NameAvatarCell's structure - PublicationDashboard: provide expandAllReviewers ref and add an expand/collapse-all button in the table's top-after slot; top-align all body cells so multi-row reviewer lists don't push siblings down
Backend:
- Add search argument to Publication.submissions field
- PaginatePublicationSubmissions resolver: apply search before the
statusCounts snapshot so counts reflect the search
- Parse prefixed search syntax in the resolver:
submitter:term matches submitter name/email/username
reviewer:term matches any reviewer
coordinator:term matches review coordinator
(plain term) matches submission title (default)
Frontend:
- Pass $search variable through the dashboard's GraphQL query so
QueryTable auto-detects the searchable capability and renders its
built-in search input
- Add searchHint prop to QueryTable: renders the hint in its own div
below the toolbar row so the input field's height stays constant
and buttons can stretch to match the input, not the hint; wires the
input to the hint via aria-describedby with a Vue useId() for
multi-instance uniqueness
- Dashboard: pass a translated search_hint describing the syntax
Simple text cells (title) sat at the top of the row while avatar cells sat lower because q-item vertically centers its content within a 48px min-height. Wrap plain content in the same q-item structure so every column uses the same vertical rhythm. - Add a generic TextCell that wraps scope.value in a q-item label - Use TextCell for the Title column - Restructure StatusBadgeCell to wrap the badge in a matching q-item - Drop the padding-top:16px override on body td; no longer needed now that all cells use q-item
…in length, fulltext indexes Search behavior: - Default search (no prefix) now matches submission title OR any assigned user (submitter, reviewer, coordinator), not just title - Add user: prefix to search all roles at once without matching title - title:, submitter:, reviewer:, coordinator: remain for single-field narrowing - Escape %, _, and \ in LIKE values so users can't accidentally (or intentionally) inject SQL LIKE wildcards - Short-circuit searches shorter than 3 characters on the server to keep high-frequency single-character queries from scanning Migration: - Add MySQL FULLTEXT indexes on submissions.title and users.name, users.email, users.username. LIKE doesn't use them yet, but they're in place for a future switch to MATCH...AGAINST when search volume justifies it. Client hint: - Update the search hint to document the 3-character minimum and the narrowing prefixes
Add a status-change dropdown directly on the status badge in the submissions table, so publication admins and editors can transition a submission's status from the dashboard without navigating into it. - Add a useStatusTransitions composable with a declarative state machine (statusTransitions map) listing the legal next states and their action names; includes a reviewer-role check - Refactor StatusBadgeCell: the badge is keyboard-focusable and opens a q-menu rendered via v-for over the transitions returned by the composable (no nested v-if conditionals); row click is stopped from propagating so clicking the badge doesn't navigate - Reuse the existing ConfirmStatusChangeDialog to perform the mutation and show feedback
EXPIRED submissions still need a decision from the editor (accept, request resubmission, or reject), so semantically they belong in the Needs Action category rather than Completed. With EXPIRED out of Completed, that category is now purely closed/terminal states: ACCEPTED_AS_FINAL, REJECTED, ARCHIVED, DELETED.
Drafts are the author's private work-in-progress and should not be visible to publication admins/editors until the author submits. - PaginatePublicationSubmissions: exclude DRAFT from the publication submissions query before the base-query snapshot - Publication::getSubmissionStatusCounts: exclude DRAFT from the global status counts - Remove DRAFT from the Awaiting Author dashboard category - Add test: testDraftsAreHiddenFromPublicationSubmissions verifying both the paginated data and status counts omit DRAFT - Update existing tests for the new DRAFT exclusion
Adds a dismissible banner to the top of /dashboard that links to /manage for users who can actually use it — anyone with a publication_admin or editor role on at least one publication, plus application administrators. A small GraphQL probe (first: 1) on the publications query with `my_role: [publication_admin, editor]` gates visibility; app admins skip the probe and always see the banner. Dismissal persists in Quasar localStorage for a week, matching the AppBanner pattern.
Replaces q-table's default "No data" strip with a centered empty panel: large icon, primary message, and a one-line hint. Distinguishes between "you don't manage any publications yet" (no rows overall) and "no publications match your search" (active filter).
`PublicationBuilder::visible()` returned `public()->orWhereHas(...)` without grouping, so when the scope was composed with other filters (search, my_role, etc.) SQL's AND-before-OR precedence let public publications leak through regardless of the downstream filter. Wrap both branches in a grouped `where(fn)` so later filters AND against the whole disjunction. Adds a regression test pinning that a search term narrows the result set across both public and assigned publications.
The previous override baked `csrf_token()` into a meta tag at page render and passed it as a static header to the GraphiQL fetcher. Laravel rotates the session token on login, so after running any login mutation in GraphiQL the in-memory header was stale and every subsequent request returned 419 Page Expired. Replace the static header with a custom `fetch` that reads the XSRF-TOKEN cookie per-request. Laravel rewrites that cookie on every response via AddQueuedCookiesToResponse, so token rotations are picked up automatically.
Drop the `my_role: [PublicationRole!]` arg from the global publications query and move /manage to `currentUser.publications`. Authorization is now implicit (you only ever see your own assignments) and the visibility policy no longer has to juggle three different consumer shapes inside `visible()`. Backend - Extend `User.publications` with `search: String @scope` and `orderBy: [PublicationAssignmentOrderBy!]`, mirroring `User.submissions`. New `PublicationAssignmentOrderBy` input type + enum sortable over NAME / CREATED_AT / UPDATED_AT. - Add `search()` and `orderByPublication()` methods on `PublicationAssignmentBuilder` that traverse through the related publication. - Remove `PublicationBuilder::myRole()` — no callers after the schema change. - Drop `my_role` from `Query.publications`. Client - `QueryTable` column `field` strings now accept dot-paths (e.g. "publication.name") so callers don't have to hand-roll accessor functions for nested shapes. - `PublicationNameCell` reads the assignment shape first and falls back to the flat Publication shape, so it works with both consumers (/manage + admin user-detail). - `/manage` page and the dashboard's "managed-publications" probe query now hit `currentUser.publications(...)`.
The auto-generated typed-router.d.ts only tracks file-based routes (src/routes/**). The submission:export:html route is still declared in routes.ts, so it was missing from the RouteNamedMap — router-link targets with that name failed TS checking. Add it to the existing manual-routes.d.ts augmentation alongside its sibling submission:export, and annotate the two computed route objects with `as const` on the name so the literal type propagates through the computed's inferred return. vue-tsc --noEmit now passes clean.
Adds a new filter to `User.publications` that restricts assignments
to publications currently holding at least one non-draft submission
in any of the given statuses. Composes naturally with the existing
`roles`, `search`, and `orderBy` filters.
Matches the SubmissionAssignmentBuilder pattern: the scope traverses
`whereHas('publication.submissions', …)` with a nested status check
and a draft exclusion, so the filter respects the same "drafts
never count" rule as the dashboard's status-count roll-ups.
Enables /manage stage-heading deep linking and the dashboard's
needs-attention publications table.
Pinned by a new PublicationTest covering the three-case matrix
(matching public, non-matching public, non-matching
draft-only assigned) and confirming the filter returns only the
matching row.
/manage — URL-synced stage filter
- Stage group headers in the table (Screening / Reviewing /
Decision / Closed) are now clickable buttons that toggle a
`?statuses=[…]` URL parameter, driving the new `with_statuses`
arg on User.publications. The row set narrows to publications
with activity in the selected stage.
- Active stage reads with an underline + bold weight; a
removable q-chip above the table reflects the filter and clears
it on dismiss. Empty state branches three ways (no data, search
mismatch, filter mismatch).
- Fix grid-mode stage counts: `countStatuses` was unwrapping the
assignment row via `pub(row)`, but `stageGroups` passes a
Publication directly. Move the unwrap to the call sites so both
shapes compose correctly.
/dashboard — needs-attention publications table
- New `NeedsActionPublicationsTable` molecule shows up to `limit`
(default 3 on the dashboard) publications the user manages that
currently have needs-action submissions, sorted by total count
desc. Per-stage columns break down the activity. `+N` overflow
chip + "Manage my publications" CTA appear when the total
exceeds the limit.
- When the user has zero publications needing action, the card
mirrors the same structure with a positive-colored header
("All caught up") and a link to the manage index — keeps the
"needs your attention" zone visible as a persistent surface
that flips between states.
- Hides during the initial query load to avoid flashing the
all-clear state before data resolves.
- Structured so a future user-preference gate can toggle the
whole component with a single v-if wrap.
Shared — RoleBadge atom + dark-mode link contrast
- Extract `RoleBadge` (src/components/atoms/) as the single
source of truth for publication role chips. Filled, not
outlined, with explicit text-color so contrast clears WCAG AA
in both light and dark modes. PublicationNameCell, the manage
grid card, and the needs-attention table all use it.
- Lighten $dark-primary from #537FCA to #8AB4F8. `.text-primary`
already routes through this in dark mode, so every themed link
and CTA across the app (dashboard banner, publication title
links, admin hint, etc.) picks up comfortable contrast against
the dark surface (~10:1).
# Conflicts: # backend/database/seeders/PublicationSeeder.php
PublicationBuilder::public() and ::acceptingSubmissions() each hardcoded `where(..., true)` and ignored the boolean argument Lighthouse's @scope directive passes in. The admin publications filter could only return accepting=true / public=true rows regardless of the user's selection. Both methods now take `bool $value = true` and apply it. The existing internal caller (PublicationBuilder::visible's `->public()`) keeps working via the default.
Align the admin filter panels with the inline-filter-in-header pattern used by feature/publication-admin's manage dashboard: - Filter button: flat/dense/no-caps with `filter_list` icon, sitting in the table's `#top-after` slot alongside the other header controls instead of occupying its own row above the table. - Active-count label: "Filter · N active" when any dimension is off-default, plain "Filter" otherwise, so the toolbar signals how much the user's view diverges from the default set. - Single top-level All/None/Invert btn-group at the top of the menu. Removes the per-sub-panel Select: All/None controls inside Status / Roles, and the redundant outer Reset Filters / Reset filters to default buttons. Internal checkbox rendering stays on q-option-group; the alternatives (q-list/q-item, q-btn-dropdown, @click.stop wrapper, :auto-close=false) did not resolve the menu-closes-on-checkbox-click behaviour, so none was worth keeping. That behaviour is pre-existing and parked for a separate investigation.
Port the conventions from feature/publication-admin so pages can be
colocated under src/routes/ and declare their own breadcrumb crumbs
via `definePage`:
Routing
- Add the VueRouter Vite plugin (`vue-router/vite`, already in deps
via vue-router@5) with `src/routes` as the routesFolder and a
generated typed-router.d.ts for IDE/type-check support.
- src/router/routes.ts now spreads `...autoRoutes` into the
MainLayout children so file-based pages inherit `requiresAuth`.
- Add router/manual-routes.d.ts to augment RouteNamedMap with the
entries still declared manually in routes.ts (publication, setup,
submission/*), so `<router-link :to="{ name }">` stays typed.
- Scope eslint's vue/multi-word-component-names rule off for
src/routes/**/*.vue (file names tie to URL segments).
- Add src/routes/**/*.vue to the graphql-codegen document glob so
colocated graphql() calls register.
Breadcrumbs
- src/use/breadcrumbs.ts: composable that walks route.matched,
builds crumbs from `meta.crumb` (single or array), and supports
dynamic label overrides via setCrumbLabel for async-loaded names.
- src/components/BreadCrumbs.vue: q-breadcrumbs renderer.
- MainLayout mounts <bread-crumbs> so all auth'd pages get the
same surface for free.
Supporting fixes
- Stub `definePage` as a global no-op in the vitest setup file so
SFCs under src/routes/ can mount under test without pulling the
VueRouter plugin into the vitest build.
- Narrow route-name literals (`as const`) / route.params accesses
(`as Record<string, string>`) in pre-existing pages that the
generated typed-router now flags (AcceptInvite, VerifyEmail,
ResetPassword, SubmissionExport, Publication/SetupLayout).
Move the manually-configured admin pages under src/pages/Admin and
layouts/Admin into the file-based routing convention that landed
with the previous commit. Each route file uses `definePage(...)` to
declare its name, `requiresAppAdmin` meta (or inherits from parent),
and its breadcrumb crumb, replacing inline q-breadcrumbs blocks.
Route mapping (all route names preserved so call-sites don't change):
- /admin → routes/admin/index.vue (admin:dashboard)
- /admin/users → routes/admin/users.vue (admin:users)
- /admin/publications → routes/admin/publications.vue (admin:publication:index)
- /admin/user/:id → routes/admin/user/[id].vue (layout, admin:user:id)
+ routes/admin/user/[id]/index.vue (user_details — publications tab)
+ routes/admin/user/[id]/submissions.vue (user_details:submissions)
- routes/admin.vue is an intermediate layout that holds the shared
`requiresAppAdmin` gate and the root "Administration" crumb, so
children only declare their own leaf crumb.
- The [id].vue layout owns the user-card header, the tab bar, and
the user-detail query. It uses setCrumbLabel to swap a placeholder
"User" crumb for the resolved user's name once the query resolves.
- Children read `id` from the typed useRoute(name) instead of a
defineProps prop, matching the convention.
- Filter-panel components at pages/Admin/components/ stay put; the
migrated pages import them via their absolute src/pages/Admin/...
path.
- Tests colocated with each route file; updated mocks to supply
`route.params` for the [id] children.
Adds MIN_SEARCH_LENGTH short-circuit and LIKE-wildcard escaping to PublicationBuilder::search, delegates the assignment builder to it so the guards live in one place, and lands a FULLTEXT index on publications.name to match the submissions/users treatment and leave the door open for a MATCH...AGAINST switch. Test pins the min-length and wildcard-escape behavior.
…e empties
- Gate the pattern overlay on both the manage-index grid chip and the
table-view CategoryCountCell behind the global .a11y-patterns /
prefers-contrast:more rules, and wrap the number in a
pattern-text-mask span so the digit keeps a solid halo when the
pattern does kick in.
- Shrink empty-state count columns on the manage table so the
Publication column absorbs the remaining width, keeping the layout
close to the populated view.
- Surface a search-hint on the manage table ("Enter at least 3
characters...") to match the backend min-length guard.
- Constrain NeedsActionPublicationsTable width via the dashboard
section grid instead of a hard max-width on the card.
…re/publication-admin # Conflicts: # client/codegen.ts # client/eslint.config.js # client/src/router/manual-routes.d.ts # client/src/typed-router.d.ts
Add a new tabbed submission detail page at /manage/publication/:id/submissions/:submissionId and reorganize review-team assignment around a popup picker so admins see workload context, recency, and pending invitees up front.
Page (routes/manage/publication/[id]/submissions/[submissionId].vue):
- Co-located GetManagedSubmission query (trimmed to fields actually rendered, with a co-located ManageSubmissionAuditFields fragment)
- Header card with id, inline-editable title, status badge, action button
- Tabs for Details, Review Team, Activity; active tab synced to ?tab= for deep linking
- Activity tab uses q-timeline with role-aware icons and dual absolute/relative timestamps
- "Action needed" flag on the Review Team tab when the coordinator slot or reviewers list is empty, with a tooltip naming the gap
New components (pages/Publication/components/):
- SubmissionAssigneeList: linked names + assigned-X-ago caption + per-row remove; staged users get a folded-corner triangle with pending icon and "Invited" badge, both with tooltips
- AssignReviewTeamDialog: popup picker over publication.users(roles: [<role>]), top-10 sorted by LAST_ASSIGNED_AT, role-aware active counts, free-email invite, two-column layout in multi mode (search/list left, selected list + shared message right)
- ManageSubmissionAudit: q-timeline-entry wrapper around the existing description rendering
- ManageTabHeader: tab content with optional action-needed flag + reason tooltip; exports ACTION_NEEDED_TAB_CLASS so the tab itself adopts the warning treatment
- ManageInfoCallout: dismissable explainer panel (pale-blue, localStorage-persisted) used on the team-tab and invited-tab pages
Backend:
- last_assigned_at scalar field on PublicationUser (max submission_user.created_at, scoped to the same roles filter as the parent users() query)
- LAST_ASSIGNED_AT added to QueryPublicationUsersOrderByColumn
- addReviewers mutation: bundled connect + invite_emails + shared message in one transaction so a partial failure can't leave a half-saved assignment set
- Carbon parsing on the recency selectSub so Lighthouse's DateTimeUtc scalar serializes correctly
Other:
- Shared .section-heading SASS class to standardize subsection headings across the manage interface
- BreadCrumbs.vue truncates long crumb labels with a tooltip fallback
- Existing dashboard/team/submitter linkTo's now route to the new manage submission page; SubmissionCard derives the publication id from the route to do the same in grid view
- Explainer callouts on the Review Team and Invited tabs make the "no add-here button" workflow explicit
- New i18n keys under publication.manage.{assign_team,review_team} plus submission.{tabs,team_alert,assignee_list}
Polish pass over the tabbed submission detail page introduced in a60f53e, plus defensive fixes for nullable content and a demo-data prereq. UI consolidation: - New SectionHeader atom and ManagePanel component capture the "title + count + missing flag + action" header row and the "bordered card with header + separator + body" panel pattern that had drifted across the three tabs - All three submission tabs (Details, Review Team, Activity) wrap their card content in ManagePanel; outer Review Team header sits at h2 above the role sub-section h3s for natural hierarchy - SubmissionAssigneeList uses SectionHeader internally and exposes a `headerless` prop so a wrapping ManagePanel can own the heading; drops per-row separators and aligns rows flush with the section heading instead of stepping in another notch New Overview panel (Details tab): - Surfaces submitted_at, updated_at, and created_by (avatar + linked name) in a definition-list layout matching the user-detail stat strips' typography - Query gains created_at, updated_at, submitted_at, created_by Submission header redesign: - ID number sits as a tucked-corner chip flush with the card border - Status row becomes a colored footer with the status icon/label and a transition dropdown when permitted on the left, plus an Actions menu (review/preview + export) on the right; replaces the separated badge + button row Defensive fixes: - SubmissionContent.vue optional-chains submission.value?.content?.data so the editor doesn't throw before content has loaded or when a submission has no content yet - DashboardDemoSeeder seeds content history on demo submissions and points submission.content at the latest entry so the legacy preview page renders against demo data - Submitter and team-member detail pages migrate from q-banner to the shared ManageInfoCallout for staged-user explainer consistency i18n: new submission.overview.{heading, submitted, not_submitted, last_updated, created_by} keys; submission.actions_menu added.
…saves
Submission::getSubmittedAt() chained Collection::where('old_values',
'like', '%"status":0%') against the eager-loaded audits relation,
which silently never matched: Illuminate\Support\Collection has no
LIKE operator (unknown operators fall through to a no-match), and
the audit's old/new values are JSON-cast to arrays rather than
strings. Inspect the parsed arrays directly via data_get and pick
the first DRAFT->INITIALLY_SUBMITTED transition. Unblocks every
consumer of submitted_at — the new manage Overview panel, the
dashboard "Submitted" column, and the GraphQL field generally.
CreatedUpdatedBy trait read auth()->user()->id unconditionally and
crashed any model save outside an authenticated request. Use
auth()->user()?->id and skip the auto-update when no user is
present so seeders, queue workers, and artisan commands can save
models that explicitly set created_by/updated_by themselves.
DashboardDemoSeeder previously synthesized the DRAFT->INITIALLY_SUBMITTED
audit row only in the primary publication's loop, leaving the
~27 supplemental-publication submissions (B/C/D/E sets) without
the audit they need for getSubmittedAt() to resolve. Extract a
synthesizeSubmittedAudit() helper that skips drafts and call it
from both loops so every non-draft demo submission has the row.
Add backend mutations and client UI for theme/color-blind preferences, per-key dismissable UI elements, and feature opt-in toggles. Includes a new Lab Features page, an admin-side user settings tab, and full test coverage on both ends.
The feature adds non-color cues for status, which benefits more than just color-blind users. Rename the GraphQL field, JSON storage key, TypeScript composable export, body class (`body--a11y-patterns`), and i18n labels to match. Also relabel the user-settings dismissed section header to plain "Dismissed help messages" so the ampersand stops escaping in the rendered DOM.
The pattern rules in app.sass gate on `.a11y-patterns`, but the composable was applying `.body--a11y-patterns`, so toggling the preference had no visible effect on dashboard pages.
Co-authored-by: Copilot <copilot@github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
WIP reference PR for the publication-admin work stacked on
feature/admin-tables.Scope: 119 files, ~17.7k insertions, 129 commits.
Intended split
This branch will be broken into smaller PRs roughly in this order:
PublicationAssignmentBuilder,PublicationBuilderadditions,PublicationPolicy,Publicationmodel.PublicationBuildersearch scope.UserSettingsmutation,SettingsPage,LabFeaturesPage,userPreferences/applyUserPreferencescomposables, lab preview images, admin user settings tab.EmptyState,NeedsActionPublicationsTable,SectionHeader,RoleBadge.statusCategories,DashboardStatusFilter,StatusBadgeCell,submissionStatusTransitions,SubmissionStatusCountsFieldresolver.SubmissionStatusBar,UserProfileCard.SubmissionCard,PublicationHomePage, breadcrumb/setup polish.PublicationUserDTO, paginators (PaginatePublicationSubmissions,PaginatePublicationUsers,PaginateSubmissions,PublicationUser,PublicationUserSubmissions),AddReviewersmutation,SubmissionPaginator, schema, tests.routes/manage/index.vue, publication shell +(tabs),ManagePanel,ManageInfoCallout,ManageTabHeader.(tabs)/team,(tabs)/invited,dashboard.vue,dashboard/{index,submissions,submitters}.submissions/[submissionId].vue,AssignReviewTeamDialog,SubmissionAssigneeList,ManageSubmissionAudit.submitters/[userId],team/[userId].DashboardDemoSeeder,UserSeeder,phpunit.xml,.env.testing.Cross-cutting risks
client/src/graphql/schema.graphql(+312) regenerates each PR — expect churn.i18n/en-US.json(+319) — split keys per feature.typed-router.d.tsregenerates from new route files — land alongside producers.Models/{Publication,User,Submission}.phpaccumulate across PRs 1/3/5/8 — strict order.Test plan