Add full achievement definition and character achievement editors - #90
Add full achievement definition and character achievement editors#90fryguy503 wants to merge 5 commits into
Conversation
* Add achievement definition and character editors Add full achievement graph authoring with categories, criteria, rewards, selectable sets, cast restrictions, references, contextual help, and fail-closed schema diagnostics. Add guarded character achievement inspection and repair operations with split-database support, stable identity enforcement, concurrency controls, audit coverage, and offline safety checks. Wire World Data and administration navigation, register least-privilege resources, add Go and Playwright regression suites, and enable branch-selected Beta release builds in GitHub Actions. * Fix achievement editor loading queries Mark manually hydrated graph relations as non-persistent GORM fields so definition, component, and reward-set reads do not attempt invalid association mapping. Replace the MariaDB-reserved character alias in character summary queries and cover both failures with MySQL dry-run regression tests. * Prepare achievement query fix release notes * Render item icons in achievement lookups Return the structured items.icon value from bounded achievement lookups and render it through Spire's native small item sprite classes, with a missing-icon fallback. Add backend contract and Playwright coverage and prepare the v5.6.2 Beta fix notes. * Fix achievement item lookup layout * Return Workflow back to pre modifications --------- Co-authored-by: Trust <trust@bastiongame.com>
This comment has been minimized.
This comment has been minimized.
|
Disposition for the CodeRabbit summary and the independent review nitpicks:
Validation: controller tests pass, production frontend build passes, and both affected Playwright suites pass 28/28. Codex |
Align achievement definition and character state management with EQEmu schema updates 9329 and 9330. Migrate to provider-neutral reward sources, preserve shared catalog identities, support version zero and pending state updates, and refresh validation, safety controls, help, and regression coverage.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/app/achievements.ts (1)
718-722: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winExclude inactive selectable mappings from the runtime snapshot.
Line 719 includes mappings for enabled rewards even when
source_enabled, the reward set, or the mapped option is disabled. These mappings are inactive runtime state.Gate
mapped_rewardswith the same source, set, and enabled-option checks used forreward_set. This prevents inactive editor changes from changing the runtime policy snapshot.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/app/achievements.ts` around lines 718 - 722, Update the mappedRewards construction to retain only mappings whose source, reward set, and mapped option are enabled, reusing the same eligibility checks used for reward_set. Apply these checks before filtering enabled reward tokens so inactive editor mappings cannot enter the runtime snapshot, while preserving the existing canonical ID mapping and runtimeSort behavior.
🧹 Nitpick comments (2)
internal/http/controllers/achievement_editor_schema_test.go (1)
91-103: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert index definitions, not only index counts.
These checks pass if an index uses incorrect columns or loses its unique constraint. Assert the column lists and uniqueness for
reward_option_entries,reward_source_entries, andcharacter_achievement_pending_updates.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/http/controllers/achievement_editor_schema_test.go` around lines 91 - 103, The test TestAchievementEditorSchemaSpecMatchesFinalSourceIndexes currently verifies only index counts; update it to inspect each index definition for reward_option_entries, reward_source_entries, and character_achievement_pending_updates, asserting the expected column lists and unique constraints in addition to the counts.internal/http/controllers/achievement_editor_repository.go (1)
606-618: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winHandle a missing global presentation-count row for a shared component.
Take(&stored)returnsgorm.ErrRecordNotFoundwhenachievement_associationshas no row forcomponent.ComponentID. The save then fails with a bare record-not-found error instead of a field error.loadDefinitionshows that count rows can be absent for existing component IDs, so this path is reachable.♻️ Proposed handling
- if err := tx.Table("achievement_associations").Clauses(clause.Locking{Strength: "UPDATE"}). - Where("component_id = ?", component.ComponentID).Take(&stored).Error; err != nil { - return err - } - if stored.RequiredCount != component.PresentationCount { + countResult := tx.Table("achievement_associations").Clauses(clause.Locking{Strength: "UPDATE"}). + Where("component_id = ?", component.ComponentID).Take(&stored) + if countResult.Error != nil && !errors.Is(countResult.Error, gorm.ErrRecordNotFound) { + return countResult.Error + } + if countResult.Error == nil && stored.RequiredCount != component.PresentationCount {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/http/controllers/achievement_editor_repository.go` around lines 606 - 618, Update the shared-component validation around the local stored struct and Take call to handle gorm.ErrRecordNotFound as a field validation error for components.<index>.presentation_count, using the same 422 error style as the existing count-mismatch branch. Preserve propagation of other database errors and the current mismatch validation for found rows.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/app/achievements.ts`:
- Around line 446-453: Preserve the selectable reward mapping order in the
normalization block by returning mapping.sequence directly instead of the linked
reward’s global sequence. In the mapped_rewards snapshot construction near the
mapped reward handling, include each mapping’s sequence so order-only changes
affect the policy snapshot.
In `@internal/http/controllers/achievement_editor_repository.go`:
- Around line 327-335: The achievementEditorRewardSet field SourceCount is
ignored by GORM, so the source_count query alias is never populated. Update its
GORM mapping to be read-only while retaining the source_count column name,
allowing the query in the reward-set lookup to populate it before Shared is
calculated.
---
Outside diff comments:
In `@frontend/src/app/achievements.ts`:
- Around line 718-722: Update the mappedRewards construction to retain only
mappings whose source, reward set, and mapped option are enabled, reusing the
same eligibility checks used for reward_set. Apply these checks before filtering
enabled reward tokens so inactive editor mappings cannot enter the runtime
snapshot, while preserving the existing canonical ID mapping and runtimeSort
behavior.
---
Nitpick comments:
In `@internal/http/controllers/achievement_editor_repository.go`:
- Around line 606-618: Update the shared-component validation around the local
stored struct and Take call to handle gorm.ErrRecordNotFound as a field
validation error for components.<index>.presentation_count, using the same 422
error style as the existing count-mismatch branch. Preserve propagation of other
database errors and the current mismatch validation for found rows.
In `@internal/http/controllers/achievement_editor_schema_test.go`:
- Around line 91-103: The test
TestAchievementEditorSchemaSpecMatchesFinalSourceIndexes currently verifies only
index counts; update it to inspect each index definition for
reward_option_entries, reward_source_entries, and
character_achievement_pending_updates, asserting the expected column lists and
unique constraints in addition to the counts.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b274b18f-fab0-4d88-ab25-a4e02c05f08a
📒 Files selected for processing (23)
frontend/src/app/achievements.tsfrontend/src/assets/css/achievement-editor.cssfrontend/src/views/achievements/AchievementEditor.vuefrontend/src/views/admin/character-achievements/CharacterAchievementEditor.vueinternal/http/controllers/achievement_editor_concurrency.gointernal/http/controllers/achievement_editor_concurrency_test.gointernal/http/controllers/achievement_editor_controller.gointernal/http/controllers/achievement_editor_http.gointernal/http/controllers/achievement_editor_metadata.gointernal/http/controllers/achievement_editor_mutations.gointernal/http/controllers/achievement_editor_repository.gointernal/http/controllers/achievement_editor_schema.gointernal/http/controllers/achievement_editor_schema_test.gointernal/http/controllers/achievement_editor_types.gointernal/http/controllers/achievement_editor_validation.gointernal/http/controllers/achievement_editor_validation_test.gointernal/http/controllers/character_achievement_editor_http.gointernal/http/controllers/character_achievement_editor_mutations.gointernal/http/controllers/character_achievement_editor_mutations_test.gointernal/http/controllers/character_achievement_editor_service.gointernal/http/controllers/character_achievement_editor_service_test.gotests/achievement-editor.spec.tstests/character-achievement-editor.spec.ts
🚧 Files skipped from review as they are similar to previous changes (17)
- internal/http/controllers/character_achievement_editor_http.go
- internal/http/controllers/achievement_editor_metadata.go
- frontend/src/assets/css/achievement-editor.css
- internal/http/controllers/achievement_editor_schema.go
- internal/http/controllers/achievement_editor_http.go
- internal/http/controllers/achievement_editor_types.go
- internal/http/controllers/achievement_editor_controller.go
- internal/http/controllers/achievement_editor_concurrency_test.go
- internal/http/controllers/achievement_editor_concurrency.go
- internal/http/controllers/character_achievement_editor_service.go
- frontend/src/views/achievements/AchievementEditor.vue
- internal/http/controllers/character_achievement_editor_mutations_test.go
- internal/http/controllers/achievement_editor_validation_test.go
- tests/achievement-editor.spec.ts
- internal/http/controllers/achievement_editor_mutations.go
- internal/http/controllers/achievement_editor_validation.go
- internal/http/controllers/character_achievement_editor_mutations.go
| sequence: (() => { | ||
| const token = stringValue(mapping.reward_id) | ||
| const reward = token.charAt(0) === '@' | ||
| ? (source.rewards || [])[Number(token.slice(1))] | ||
| : (source.rewards || []).find((row: any) => stringValue(row.reward_id) === token) | ||
| return numberValue(reward && reward.sequence, numberValue(mapping.sequence)) | ||
| })(), | ||
| reward_id: stringValue(mapping.reward_id) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Preserve selectable reward mapping order.
Line 446 replaces reward_option_entries.sequence with the linked reward's global sequence. These fields have different scopes. Saving an unchanged graph can rewrite the order of grants within an option.
Line 718 also omits sequence from mapped_rewards. An order-only runtime change then does not change the policy snapshot.
Keep mapping.sequence during normalization. Include it in each snapshot mapping.
Proposed fix
- sequence: (() => {
- const token = stringValue(mapping.reward_id)
- const reward = token.charAt(0) === '@'
- ? (source.rewards || [])[Number(token.slice(1))]
- : (source.rewards || []).find((row: any) => stringValue(row.reward_id) === token)
- return numberValue(reward && reward.sequence, numberValue(mapping.sequence))
- })(),
+ sequence: numberValue(mapping.sequence),
reward_id: stringValue(mapping.reward_id) ? graph.reward_set.mappings.filter((mapping: any) => enabledRewardTokens.has(String(mapping.reward_id))).map((mapping: any) => ({
option_id: mapping.option_id,
+ sequence: mapping.sequence,
reward_id: canonicalRewardIDs.get(String(mapping.reward_id))
})).sort(runtimeSort)Also applies to: 718-722
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/app/achievements.ts` around lines 446 - 453, Preserve the
selectable reward mapping order in the normalization block by returning
mapping.sequence directly instead of the linked reward’s global sequence. In the
mapped_rewards snapshot construction near the mapped reward handling, include
each mapping’s sequence so order-only changes affect the policy snapshot.
| set := achievementEditorRewardSet{Options: make([]achievementEditorRewardOption, 0), Mappings: make([]achievementEditorRewardMapping, 0)} | ||
| setResult := r.db.Table("reward_sources source"). | ||
| Select(`reward_set.reward_set_id, reward_set.title, reward_set.enabled, | ||
| source.enabled AS source_enabled, | ||
| (SELECT COUNT(*) FROM reward_sources usage_source WHERE usage_source.reward_set_id = source.reward_set_id) AS source_count`). | ||
| Joins("JOIN reward_sets reward_set ON reward_set.reward_set_id = source.reward_set_id"). | ||
| Where("source.source_type = ? AND source.source_id = ?", achievementEditorRewardSourceType, id).Take(&set) | ||
| if setResult.Error == nil { | ||
| set.Shared = set.SourceCount > 1 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
GORM struct tag "-" ignore field read and write, "->" read-only permission tag
💡 Result:
In GORM, you can control field-level read and write permissions using the gorm struct tag [1][2]. To create a read-only field (allowing database reads but preventing GORM from writing/updating that field), you should use the -> permission tag [1][3]. For precise control, you can combine the -> (read) and <- (write) operators within the gorm tag [1][4]. Read-only Configuration: To make a field read-only, use: type User struct { Name string gorm:"->" // Read-only: GORM will read but never write/update this field } Permission Tag Reference: - ->: Allows read access [1]. - ->:false: Explicitly disables read access [1][5]. - <-:false: Disables write access (create/update) for the field [1][6]. - gorm:"-": Completely ignores the field (no read, no write, no migration) [1][2]. - gorm:"-:migration": Includes the field in standard CRUD operations but ignores it during AutoMigrate [1][7]. If you want a field to be read-only while still being created in the database schema, use gorm:"->;<-:false" [1][8]. Note that if you use gorm:"-", GORM completely ignores the field for all database operations, meaning it will not be populated by queries and will not be created in the database [1][9][3].
Citations:
- 1: https://gorm.io/docs/models.html
- 2: https://mintlify.wiki/go-gorm/gorm/models/declaring-models
- 3: Field with gorm:"-" is not set in query go-gorm/gorm#3089
- 4: https://www.bookstack.cn/read/gorm-1.23.5-en/04749ab8c3d0ea36.md
- 5: undocumented breaking change in gorm v2, the value of the ignored field gorm:"-" in struct is not updated when use Update(). go-gorm/gorm#5386
- 6: Set read-only field using OnConflict clause go-gorm/gorm#6173
- 7: Add field tag to ignore migration go-gorm/gorm#4028
- 8: How to scan ignored field gorm:"-" ? go-gorm/gorm#5178
- 9: How to Select ignored field gorm:"-" ? go-gorm/gorm#5284
🏁 Script executed:
rg -n -C 8 'type achievementEditorRewardSet|SourceCount|source_count|Shared' internal/http/controllers/achievement_editor_repository.go go.mod go.sumRepository: Valorith/spire
Length of output: 11272
🏁 Script executed:
rg -n -S -C 10 'achievementEditorRewardSet|gorm:"-"' --glob '*.go' .Repository: Valorith/spire
Length of output: 50370
🏁 Script executed:
files=$(rg -l --glob '*.go' 'type achievementEditorRewardSet')
printf '%s\n' "$files"
for f in $files; do
rg -n -A 25 -B 5 'type achievementEditorRewardSet' "$f"
done
printf '\nGORM dependencies:\n'
rg -n 'gorm.io/gorm|gorm.io/driver' go.mod go.sumRepository: Valorith/spire
Length of output: 152
🏁 Script executed:
rg -n --hidden --glob '!vendor/**' --glob '!node_modules/**' 'achievementEditorRewardSet|SourceCount' .
git ls-files | rg 'achievement_editor|go.mod|go.sum'Repository: Valorith/spire
Length of output: 5770
🏁 Script executed:
sed -n '88,116p' internal/http/controllers/achievement_editor_types.go
printf '\nGORM versions:\n'
rg -n 'gorm.io/gorm|gorm.io/driver' go.mod go.sum | head -20
printf '\nExisting hydration tests:\n'
sed -n '1,70p' internal/http/controllers/achievement_editor_repository_test.goRepository: Valorith/spire
Length of output: 4765
🏁 Script executed:
mod=$(go env GOPATH 2>/dev/null)/pkg/mod/gorm.io/gorm@v1.24.0
if [ -d "$mod" ]; then
printf 'GORM source: %s\n' "$mod"
rg -n -C 6 'TagSettings.*-|\["-"|Permission|ParseField|SchemaField' "$mod/schema" | head -160
else
printf 'GORM source is not available at %s\n' "$mod"
fiRepository: Valorith/spire
Length of output: 15820
🏁 Script executed:
mod=$(go env GOPATH)/pkg/mod/gorm.io/gorm@v1.24.0
rg -n -C 8 'Readable|FieldsByDBName|ScanInitialized|ColumnMapping' "$mod/callbacks" "$mod/scan.go" "$mod/schema" | head -220Repository: Valorith/spire
Length of output: 21686
Make SourceCount read-only instead of ignored
gorm:"-" prevents GORM from scanning the source_count alias, so set.Shared remains false for shared reward sets. Use gorm:"->;column:source_count" or scan the count separately.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/http/controllers/achievement_editor_repository.go` around lines 327
- 335, The achievementEditorRewardSet field SourceCount is ignored by GORM, so
the source_count query alias is never populated. Update its GORM mapping to be
read-only while retaining the source_count column name, allowing the query in
the reward-set lookup to populate it before Shared is calculated.
Add type 6 specific-AA ranks and type 7 class-ineligible fallback authoring, validation, lookup, and character tooling. Bound AA rank-chain validation and add regression coverage.
DO NOT MERGE UNTIL EQEmu/EQEmu#5119 has been accepted
Summary
This PR adds a complete achievement-management feature to Spire, covering both:
The feature replaces direct SQL management with validated, transactional editors for categories, definitions, components, criteria, rewards, selectable reward sets, spell restrictions, character progress, reward ledgers, and queued achievement mutations.
All material fields include inline explanations, contextual help, or accessible descriptions so editors can understand what each value controls and how it affects the EQEmu runtime.
Motivation
Achievement definitions are stored as an interconnected graph spanning numerous tables. Editing these records manually creates significant risks:
This PR provides a guided authoring and support interface with server-side validation, optimistic concurrency, transaction boundaries, audit history, and explicit recovery workflows.
User-facing features
World Data → Achievements
Adds an Alpha/New Achievements entry to the World Data navigation group.
The achievement workspace contains three primary modes:
Definition catalog
The definition directory supports:
Directory rows summarize:
Complete definition graph editor
A definition is loaded and saved as one complete graph rather than as disconnected SQL rows.
The editor supports:
General
New definitions must be created disabled so they can be reviewed before publication.
Category associations
Components
Component types
0–2carry durable state. Type3is presentation-only and cannot contain enabled criteria.Criteria
Each component can contain nested criteria with:
The editor supports all achievement events currently implemented by EQEmu:
Event selection dynamically changes:
Supported component behaviors include:
Supported progress modes include:
Canonical skill and class values
The editor exposes:
1–160–77Dual WieldandMake Poison0as a wildcardRewards
The editor supports canonical reward grants for:
Each reward includes:
New reward IDs are allocated transactionally by the server and returned as decimal strings to preserve unsigned BIGINT precision.
Selectable reward sets
A definition can author a selectable reward set containing:
New unsaved rewards use safe transient mapping tokens until the server allocates their durable reward IDs.
Validation ensures:
Spell cast restrictions
Definitions can be connected to existing achievement-aware spell restriction IDs.
The editor supports:
Category editor
The category workspace supports:
Safety includes:
Bounded reference lookups
Reusable reference pickers are provided for:
Lookup behavior includes:
Item lookup results include the native Spire item sprite supplied by
items.icon. Achievement-specific styling prevents the EQWindow button theme from collapsing the icon grid.NPC Name Kill helper
The authoring guide and criterion editor include a helper that:
This avoids requiring authors to calculate hashes manually.
Validation and authoring education
Field metadata is supplied by the backend and consumed dynamically by the frontend.
Every material field includes:
aria-describedbylinkage where appropriateThe embedded authoring guide documents:
Authoritative server validation
Browser validation provides immediate feedback, but backend validation remains authoritative.
Validation includes:
TEXTcolumnsReference validation covers:
npc_typestaskszoneitemstradeskill_recipeskill_capsalternate_currencytitles.title_setMissing references or unavailable reference catalogs block enabled publication. Disabled drafts retain warnings so incomplete content can be safely staged.
Custom NPC race IDs remain advisory because custom engine race values may intentionally have no current
npc_typesrow.Graph and request limits
The API enforces:
Large numeric identities and values are represented as decimal strings where JavaScript
Numberwould lose precision.Transaction and concurrency safety
Definition and category writes use:
FOR UPDATErow locks409 ConflictresponsesRuntime-policy fingerprints distinguish presentation-only changes from changes that can reinterpret durable player state.
A definition-version increase is required when changing runtime evaluation, reward, mapping, or reset policy, even if the definition is temporarily disabled before being re-enabled.
Presentation-only edits do not require unnecessary version bumps.
Stable identity protection
The editor prevents unsafe identity changes after content has been deployed.
Protected identities include:
Persisted identities generally must be disabled instead of removed or renumbered.
Creating or cloning an achievement also checks durable character tables before accepting the destination ID. A deleted achievement ID cannot be reused while completion, progress, reward, selection, or pending-mutation history still references it.
Clone behavior
Cloning a definition:
CLONE <source-id>confirmation1Definition deletion behavior
Deleting a definition:
DELETE <id>confirmationDeleted-definition character state remains visible as orphaned state in the character achievement editor.
Orphan criterion recovery
If criteria exist without their owning
achievement_componentsrow, the editor does not silently hide or delete them.Instead it renders a recovery-only component and requires one explicit whole-group action:
The server rejects:
Character Achievement administration
Adds a separately permissioned administrator workspace under:
Player Operations → Character Achievements
The character directory supports:
Content and character-state databases are queried independently and assembled without cross-database joins.
Character state filters
Achievement state can be filtered by:
Search includes:
Achievement state details
Expanded definition cards show:
Character workspace tabs
The character editor contains:
The diagnostics tabs expose:
Character repair operations
All durable character mutations are intentionally offline-only.
Set exact progress
Allows an administrator to set one reviewed component count.
Safety includes:
This operation does not synthesize a game event.
Force completion
Persists a completion row at the current definition version.
It does not directly:
The game server remains responsible for runtime reconciliation.
Reset achievement state
The normal reset removes:
Reward and selection history is preserved by default to prevent duplicate delivery after recompletion.
Deleting reward history requires:
RESET REWARDS <achievement-id>confirmationReset also supports orphaned character state whose definition was deleted.
Retry individual reward
Eligible automatic reward ledgers can be returned to retryable state.
The editor:
Selectable grants must be repaired through their owning selection bundle.
Retry selectable reward bundle
Selection retry:
Retry pending mutation
Only compatible blocked rows can return to pending.
Retry requires:
Legacy, missing, disabled, or version-incompatible rows remain blocked.
Discard pending mutation
Pending and blocked mutations can be discarded after review.
Active processing rows remain locked for 60 seconds. Once the lease is stale, discard or reset requires a separate stale-processing-lease acknowledgement.
Character mutation safety
Every character mutation requires:
character_datarow lockContent-dependent mutations also hold the achievement authoring lock while reading runtime policy and committing the character mutation. This prevents a concurrent content save from invalidating the policy mid-operation.
Online characters remain inspectable, but repair buttons are disabled because the zone process owns cached runtime state.
Split-database support
The implementation deliberately separates:
eqemu_contentNo content/character cross-database join is required, so separate hosts and connections are supported.
Permissions
Adds two independent permission resources:
achievement-editorcharacter-achievement-editorThis allows content authors to manage definitions without automatically granting access to durable player-state repair operations.
Audit history
Every successful definition, category, or character mutation creates a Spire user-event audit record.
Audited operations include:
Audit records include operator attribution, reason, target identity, and before/after context.
The provisional audit record is discarded when the corresponding mutation fails or rolls back.
API endpoints
Achievement definition editor
GET/api/v1/achievement-editor/metadataGET/api/v1/achievement-editor/schemaGET/api/v1/achievement-editor/definitionsGET/api/v1/achievement-editor/definition/:idPUT/api/v1/achievement-editor/definitionPATCH/api/v1/achievement-editor/definition/:idPUT/api/v1/achievement-editor/definition/:id/cloneDELETE/api/v1/achievement-editor/definition/:idGET/api/v1/achievement-editor/categoriesGET/api/v1/achievement-editor/category/:idPUT/api/v1/achievement-editor/categoryPATCH/api/v1/achievement-editor/category/:idDELETE/api/v1/achievement-editor/category/:idGET/api/v1/achievement-editor/lookups/:kindGET/api/v1/achievement-editor/auditCharacter Achievement administration
GET/api/v1/character-achievement-editor/metadataGET/api/v1/character-achievement-editor/schemaGET/api/v1/character-achievement-editor/charactersGET/api/v1/character-achievement-editor/character/:idGET/api/v1/character-achievement-editor/character/:id/auditPATCH/api/v1/character-achievement-editor/character/:id/progressPATCH/api/v1/character-achievement-editor/character/:id/completePATCH/api/v1/character-achievement-editor/character/:id/resetPATCH/api/v1/character-achievement-editor/character/:id/reward/retryPATCH/api/v1/character-achievement-editor/character/:id/selection/retryPATCH/api/v1/character-achievement-editor/character/:id/mutation/retryDELETE/api/v1/character-achievement-editor/character/:id/mutationSchema prerequisites
This PR does not create or migrate the achievement schema.
The matching EQEmu achievement migrations must already be installed.
Required content tables
Required character-state tables
The schema probe validates:
The feature fails closed when the schema is incomplete or incompatible. Diagnostics are cached briefly and can be explicitly rechecked from the UI.
Accessibility and responsive behavior
The editors include:
The definition workspace progressively collapses its directory, forms, guide cards, and graph grids for smaller displays.
The character workspace reflows cards, actions, safety panels, and confirmation dialogs while retaining horizontal scrolling for large diagnostic tables.
Automated coverage
This PR adds:
Covered behavior includes:
Verification performed
The following targeted Go verification passes on Go 1.23:
go test -count=1 ./boot ./internal/http/controllers ./internal/permissionsA clean Node 20 production build also completes successfully:
cd frontend npm ci npm run buildThe two Playwright files contain 28 scenarios and collect successfully. Full browser execution requires a running Spire frontend:
npx playwright test \ tests/achievement-editor.spec.ts \ tests/character-achievement-editor.spec.tsDeployment notes
eqemu_contentand the character database.Intentional limitations
Change size
Summary by CodeRabbit
New Features
Bug Fixes
Tests