记录星册首页 More 按钮恢复的方案说明与 TDD 实施计划,作为本次特性 commit e079a6c 的设计依据。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
28 KiB
Starbook Home Has-More Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Restore the starbook home preview so every regular grade, collection group, and activity group returns the three highest-liked assets and sets has_more=true when additional assets exist, while the More endpoint returns every matching asset in the same order.
Architecture: Keep the existing shared group builders and add a previewLimit int parameter. A single helper sorts valid AssetItem values by authoritative models.Asset.LikeCount DESC, models.Asset.ID ASC, truncates only when previewLimit > 0, and reports whether truncation occurred. GetMyAssets passes 3; GetAssetsByType passes 0.
Tech Stack: Go 1.25, Dubbo Triple protobuf DTOs, testify, GORM/PostgreSQL integration tests.
Global Constraints
- Use
models.Asset.LikeCountas the only ordering source; never sort fromAssetRegistry.LikeCount. - Stable order is
Asset.LikeCount DESC, Asset.ID ASCfor regular, collection, and activity assets. GetMyAssetsuses the fixedhomePreviewLimit = 3.GetAssetsByTypeusespreviewLimit = 0, returns all matches, and leaves group/gradehas_more=false.- Count only registry rows that resolve to a real
models.Asset; dangling registries do not affecttotal_countorhas_more. - Do not modify
frontend/pages/components/StarbookContent.vue; its existingv-ifexpressions consume the corrected fields. - Do not modify
backend/proto/asset.protoor generated protobuf files for this feature. - Do not add a new dependency, configuration flag, cache, migration, or repository pagination abstraction.
- Do not run
git commitorgit push; repository rules prohibit AI commits unless explicitly requested.
File Structure
- Create
backend/services/assetService/service/asset_service_group_test.go: DB-free unit tests for sorting, preview truncation, valid-item counting, authoritative like source, and all three group builders. - Modify
backend/services/assetService/service/asset_service.go: add the preview constant/helper, parameterize the three builders, and pass the correct limit from the home and More flows. - Modify
backend/services/assetService/provider/asset_provider_test.go: add a DB-backed end-to-end regression provingGetMyAssetsreturns top three whileGetAssetsByTypereturns all four in the same order. - No frontend or protobuf source/generated files change.
Task 1: Add the shared ordering helper and regular-grade preview
Files:
- Create:
backend/services/assetService/service/asset_service_group_test.go - Modify:
backend/services/assetService/service/asset_service.go:22-46,159-183,309-324,371-438
Interfaces:
-
Produces:
const homePreviewLimit = 3 -
Produces:
sortAndLimitAssetItems(items []*pb.AssetItem, previewLimit int) ([]*pb.AssetItem, bool) -
Changes:
buildRegularGroupForAssets(..., previewLimit int) *pb.AssetGroup -
previewLimit <= 0means sorted but unlimited, withhasMore=false. -
Step 1: Write the failing regular preview tests
Create backend/services/assetService/service/asset_service_group_test.go:
package service
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/topfans/backend/pkg/models"
pb "github.com/topfans/backend/pkg/proto/asset"
)
func assetIDs(items []*pb.AssetItem) []int64 {
ids := make([]int64, 0, len(items))
for _, item := range items {
ids = append(ids, item.AssetId)
}
return ids
}
func rankedAssets() []*models.Asset {
return []*models.Asset{
{ID: 1, Name: "ten", LikeCount: 10},
{ID: 2, Name: "thirty-first", LikeCount: 30},
{ID: 3, Name: "twenty", LikeCount: 20},
{ID: 4, Name: "thirty-second", LikeCount: 30},
}
}
func TestBuildRegularGroupForAssets_HomePreviewUsesAssetLikes(t *testing.T) {
grade := int32(2)
registries := []*models.AssetRegistry{
{AssetID: 1, AssetType: models.AssetTypeRegular, Grade: &grade, LikeCount: 1000},
{AssetID: 2, AssetType: models.AssetTypeRegular, Grade: &grade, LikeCount: 1},
{AssetID: 3, AssetType: models.AssetTypeRegular, Grade: &grade, LikeCount: 900},
{AssetID: 4, AssetType: models.AssetTypeRegular, Grade: &grade, LikeCount: 2},
{AssetID: 999, AssetType: models.AssetTypeRegular, Grade: &grade, LikeCount: 9999},
}
group := (&assetService{}).buildRegularGroupForAssets(rankedAssets(), nil, registries, 3)
require.NotNil(t, group)
require.Len(t, group.Grades, 1)
section := group.Grades[0]
assert.Equal(t, []int64{2, 4, 3}, assetIDs(section.Items))
assert.Equal(t, int32(4), section.TotalCount, "dangling registry must not count")
assert.True(t, section.HasMore)
assert.Equal(t, int32(4), group.TotalCount)
assert.True(t, group.HasMore)
}
func TestBuildRegularGroupForAssets_UnlimitedReturnsAllSorted(t *testing.T) {
grade := int32(2)
registries := []*models.AssetRegistry{
{AssetID: 1, AssetType: models.AssetTypeRegular, Grade: &grade},
{AssetID: 2, AssetType: models.AssetTypeRegular, Grade: &grade},
{AssetID: 3, AssetType: models.AssetTypeRegular, Grade: &grade},
{AssetID: 4, AssetType: models.AssetTypeRegular, Grade: &grade},
}
group := (&assetService{}).buildRegularGroupForAssets(rankedAssets(), nil, registries, 0)
require.Len(t, group.Grades, 1)
assert.Equal(t, []int64{2, 4, 3, 1}, assetIDs(group.Grades[0].Items))
assert.False(t, group.Grades[0].HasMore)
assert.False(t, group.HasMore)
}
- Step 2: Run the tests and verify RED
Run:
go -C backend/services/assetService test -count=1 ./service -run 'TestBuildRegularGroupForAssets' -v
Expected: compilation fails because buildRegularGroupForAssets currently accepts three arguments, not four. This is the intended RED failure.
- Step 3: Add the shared preview constant and helper
In backend/services/assetService/service/asset_service.go, add near the service definitions:
const homePreviewLimit = 3
Add after registryLogicalCategory:
// sortAndLimitAssetItems applies the starbook ordering contract and optionally
// truncates a home-page preview. previewLimit <= 0 means unlimited.
func sortAndLimitAssetItems(items []*pb.AssetItem, previewLimit int) ([]*pb.AssetItem, bool) {
sort.Slice(items, func(i, j int) bool {
if items[i].LikeCount != items[j].LikeCount {
return items[i].LikeCount > items[j].LikeCount
}
return items[i].AssetId < items[j].AssetId
})
if previewLimit > 0 && len(items) > previewLimit {
return items[:previewLimit], true
}
return items, false
}
- Step 4: Parameterize and implement the regular builder
Replace buildRegularGroupForAssets with:
// buildRegularGroupForAssets 构建原创藏品分组
func (s *assetService) buildRegularGroupForAssets(allAssets []*models.Asset, registryMap map[int64]*models.AssetRegistry, registries []*models.AssetRegistry, previewLimit int) *pb.AssetGroup {
assetMap := make(map[int64]*models.Asset)
for _, asset := range allAssets {
assetMap[asset.ID] = asset
}
gradeGroups := make(map[int32][]*models.AssetRegistry)
for _, reg := range registries {
if reg.Grade != nil {
gradeGroups[*reg.Grade] = append(gradeGroups[*reg.Grade], reg)
}
}
grades := make([]*pb.GradeSection, 0)
for grade, regs := range gradeGroups {
items := make([]*pb.AssetItem, 0, len(regs))
for _, reg := range regs {
asset := assetMap[reg.AssetID]
if asset == nil {
continue
}
items = append(items, &pb.AssetItem{
AssetId: asset.ID,
Name: asset.Name,
CoverUrlSigned: asset.CoverURL,
LikeCount: asset.LikeCount,
CreatedAt: asset.CreatedAt,
Category: "castlove",
Grade: grade,
DisplayStatus: reg.DisplayStatus,
})
}
totalCount := int32(len(items))
items, hasMore := sortAndLimitAssetItems(items, previewLimit)
grades = append(grades, &pb.GradeSection{
Grade: grade,
Items: items,
TotalCount: totalCount,
HasMore: hasMore,
})
}
sort.Slice(grades, func(i, j int) bool {
return grades[i].Grade > grades[j].Grade
})
totalCount := int32(0)
hasMore := false
for _, grade := range grades {
totalCount += grade.TotalCount
hasMore = hasMore || grade.HasMore
}
return &pb.AssetGroup{
Type: models.AssetTypeRegular,
Category: "castlove",
CategoryName: "原创",
Grades: grades,
TotalCount: totalCount,
HasMore: hasMore,
}
}
Keep the existing registryMap parameter in this task to avoid an unrelated signature/refactor change; it remains part of all three existing builder contracts.
- Step 5: Pass the correct limit at regular call sites
In GetMyAssets:
if regs, ok := typeGroups[models.AssetTypeRegular]; ok {
group := s.buildRegularGroupForAssets(allAssets, registryMap, regs, homePreviewLimit)
if group != nil {
groups = append(groups, group)
}
}
In GetAssetsByType:
if regs, ok := typeGroups[models.AssetTypeRegular]; ok {
if group := s.buildRegularGroupForAssets(allAssets, registryMap, regs, 0); group != nil {
groups = append(groups, group)
}
}
- Step 6: Run the regular tests and verify GREEN
Run:
gofmt -w backend/services/assetService/service/asset_service.go backend/services/assetService/service/asset_service_group_test.go
go -C backend/services/assetService test -count=1 ./service -run 'TestBuildRegularGroupForAssets' -v
Expected: both regular tests pass; no DB is required.
- Step 7: Review checkpoint
Run:
git diff --check -- backend/services/assetService/service/asset_service.go backend/services/assetService/service/asset_service_group_test.go
git diff -- backend/services/assetService/service/asset_service.go backend/services/assetService/service/asset_service_group_test.go
Confirm the diff contains only the shared helper, regular preview behavior, call-site limits, and tests. Do not commit.
Task 2: Apply the same contract to collection and activity groups
Files:
- Modify:
backend/services/assetService/service/asset_service_group_test.go - Modify:
backend/services/assetService/service/asset_service.go:169-183,315-324,440-522
Interfaces:
-
Consumes:
sortAndLimitAssetItems(items []*pb.AssetItem, previewLimit int) ([]*pb.AssetItem, bool)from Task 1. -
Changes:
buildCollectionGroupForAssets(..., previewLimit int) *pb.AssetGroup -
Changes:
buildActivityGroupForAssets(..., previewLimit int) *pb.AssetGroup -
Step 1: Append failing collection and activity tests
Append to asset_service_group_test.go:
func strPtr(value string) *string { return &value }
func TestBuildCollectionGroupForAssets_PreviewAndUnlimited(t *testing.T) {
registries := []*models.AssetRegistry{
{AssetID: 1, AssetType: models.AssetTypeCollection, CollectionCategory: strPtr("cards")},
{AssetID: 2, AssetType: models.AssetTypeCollection, CollectionCategory: strPtr("cards")},
{AssetID: 3, AssetType: models.AssetTypeCollection, CollectionCategory: strPtr("cards")},
{AssetID: 4, AssetType: models.AssetTypeCollection, CollectionCategory: strPtr("cards")},
{AssetID: 999, AssetType: models.AssetTypeCollection, CollectionCategory: strPtr("cards")},
}
svc := &assetService{}
preview := svc.buildCollectionGroupForAssets(rankedAssets(), nil, registries, 3)
assert.Equal(t, []int64{2, 4, 3}, assetIDs(preview.Items))
assert.Equal(t, int32(4), preview.TotalCount)
assert.True(t, preview.HasMore)
full := svc.buildCollectionGroupForAssets(rankedAssets(), nil, registries, 0)
assert.Equal(t, []int64{2, 4, 3, 1}, assetIDs(full.Items))
assert.Equal(t, int32(4), full.TotalCount)
assert.False(t, full.HasMore)
}
func TestBuildActivityGroupForAssets_PreviewAndUnlimited(t *testing.T) {
registries := []*models.AssetRegistry{
{AssetID: 1, AssetType: models.AssetTypeActivity, ActivityType: strPtr("offline")},
{AssetID: 2, AssetType: models.AssetTypeActivity, ActivityType: strPtr("offline")},
{AssetID: 3, AssetType: models.AssetTypeActivity, ActivityType: strPtr("offline")},
{AssetID: 4, AssetType: models.AssetTypeActivity, ActivityType: strPtr("offline")},
{AssetID: 999, AssetType: models.AssetTypeActivity, ActivityType: strPtr("offline")},
}
svc := &assetService{}
preview := svc.buildActivityGroupForAssets(rankedAssets(), nil, registries, 3)
assert.Equal(t, []int64{2, 4, 3}, assetIDs(preview.Items))
assert.Equal(t, int32(4), preview.TotalCount)
assert.True(t, preview.HasMore)
full := svc.buildActivityGroupForAssets(rankedAssets(), nil, registries, 0)
assert.Equal(t, []int64{2, 4, 3, 1}, assetIDs(full.Items))
assert.Equal(t, int32(4), full.TotalCount)
assert.False(t, full.HasMore)
}
- Step 2: Run the tests and verify RED
Run:
go -C backend/services/assetService test -count=1 ./service -run 'TestBuild(Collection|Activity)GroupForAssets' -v
Expected: compilation fails because the collection/activity builders still accept three arguments.
- Step 3: Parameterize the collection builder
Replace the collection builder with:
// buildCollectionGroupForAssets 构建典收藏品分组
func (s *assetService) buildCollectionGroupForAssets(allAssets []*models.Asset, registryMap map[int64]*models.AssetRegistry, registries []*models.AssetRegistry, previewLimit int) *pb.AssetGroup {
assetMap := make(map[int64]*models.Asset)
for _, asset := range allAssets {
assetMap[asset.ID] = asset
}
items := make([]*pb.AssetItem, 0, len(registries))
for _, reg := range registries {
asset := assetMap[reg.AssetID]
if asset == nil {
continue
}
category := ""
if reg.CollectionCategory != nil {
category = *reg.CollectionCategory
}
items = append(items, &pb.AssetItem{
AssetId: asset.ID,
Name: asset.Name,
CoverUrlSigned: asset.CoverURL,
LikeCount: asset.LikeCount,
CreatedAt: asset.CreatedAt,
Category: category,
Grade: 0,
DisplayStatus: reg.DisplayStatus,
})
}
totalCount := int32(len(items))
items, hasMore := sortAndLimitAssetItems(items, previewLimit)
return &pb.AssetGroup{
Type: models.AssetTypeCollection,
Category: "",
CategoryName: "典藏",
Items: items,
TotalCount: totalCount,
HasMore: hasMore,
}
}
- Step 4: Parameterize the activity builder
Replace the activity builder with:
// buildActivityGroupForAssets 构建活动藏品分组
func (s *assetService) buildActivityGroupForAssets(allAssets []*models.Asset, registryMap map[int64]*models.AssetRegistry, registries []*models.AssetRegistry, previewLimit int) *pb.AssetGroup {
assetMap := make(map[int64]*models.Asset)
for _, asset := range allAssets {
assetMap[asset.ID] = asset
}
items := make([]*pb.AssetItem, 0, len(registries))
for _, reg := range registries {
asset := assetMap[reg.AssetID]
if asset == nil {
continue
}
activityType := ""
if reg.ActivityType != nil {
activityType = *reg.ActivityType
}
items = append(items, &pb.AssetItem{
AssetId: asset.ID,
Name: asset.Name,
CoverUrlSigned: asset.CoverURL,
LikeCount: asset.LikeCount,
CreatedAt: asset.CreatedAt,
Category: activityType,
Grade: 0,
DisplayStatus: reg.DisplayStatus,
})
}
totalCount := int32(len(items))
items, hasMore := sortAndLimitAssetItems(items, previewLimit)
return &pb.AssetGroup{
Type: models.AssetTypeActivity,
Category: "",
CategoryName: "活动",
Items: items,
TotalCount: totalCount,
HasMore: hasMore,
}
}
- Step 5: Pass limits at all collection/activity call sites
In GetMyAssets, pass homePreviewLimit:
if regs, ok := typeGroups[models.AssetTypeCollection]; ok {
group := s.buildCollectionGroupForAssets(allAssets, registryMap, regs, homePreviewLimit)
if group != nil {
groups = append(groups, group)
}
}
if regs, ok := typeGroups[models.AssetTypeActivity]; ok {
group := s.buildActivityGroupForAssets(allAssets, registryMap, regs, homePreviewLimit)
if group != nil {
groups = append(groups, group)
}
}
In GetAssetsByType, pass 0:
if regs, ok := typeGroups[models.AssetTypeCollection]; ok {
if group := s.buildCollectionGroupForAssets(allAssets, registryMap, regs, 0); group != nil {
groups = append(groups, group)
}
}
if regs, ok := typeGroups[models.AssetTypeActivity]; ok {
if group := s.buildActivityGroupForAssets(allAssets, registryMap, regs, 0); group != nil {
groups = append(groups, group)
}
}
- Step 6: Run all DB-free group tests and verify GREEN
Run:
gofmt -w backend/services/assetService/service/asset_service.go backend/services/assetService/service/asset_service_group_test.go
go -C backend/services/assetService test -count=1 ./service -run 'TestBuild(Regular|Collection|Activity)GroupForAssets' -v
Expected: all four group-builder tests pass without a database.
- Step 7: Review checkpoint
Run:
git diff --check -- backend/services/assetService/service/asset_service.go backend/services/assetService/service/asset_service_group_test.go
git diff --stat
Confirm no frontend, proto, generated proto, migration, or dependency file was changed by this task. Do not commit.
Task 3: Verify the home and More call paths end-to-end
Files:
- Modify:
backend/services/assetService/provider/asset_provider_test.go:89-145,206-220
Interfaces:
-
Consumes:
GetMyAssetswithhomePreviewLimit=3wired internally. -
Consumes:
GetAssetsByTypewithpreviewLimit=0wired internally. -
Verifies: both paths use
Asset.LikeCount DESC, Asset.ID ASC. -
Step 1: Add a ranked collection fixture helper
Append after sbSeed in asset_provider_test.go:
func sbAddRankedCollectionAssets(t *testing.T, db *gorm.DB) {
t.Helper()
var existing models.Asset
require.NoError(t, db.Where(
"owner_uid = ? AND star_id = ? AND name = ?",
sbTestOwnerUID, sbTestStarID, "collection_x",
).First(&existing).Error)
require.NoError(t, db.Model(&existing).Update("like_count", int32(5)).Error)
require.NoError(t, db.Model(&models.AssetRegistry{}).
Where("asset_id = ?", existing.ID).
Update("like_count", int32(999)).Error)
category := "手办"
now := time.Now().UnixMilli()
for index, likeCount := range []int32{20, 40, 30} {
asset := &models.Asset{
OwnerUID: sbTestOwnerUID,
StarID: sbTestStarID,
Name: fmt.Sprintf("collection_ranked_%d", index),
CoverURL: fmt.Sprintf("https://cdn/collection_ranked_%d.png", index),
Status: 1,
IsActive: true,
LikeCount: likeCount,
CreatedAt: now + int64(index+1),
UpdatedAt: now + int64(index+1),
}
require.NoError(t, db.Create(asset).Error)
require.NoError(t, db.Create(&models.AssetRegistry{
AssetID: asset.ID,
AssetType: models.AssetTypeCollection,
OwnerUID: sbTestOwnerUID,
StarID: sbTestStarID,
CollectionCategory: &category,
LikeCount: 1000 - likeCount,
DisplayStatus: 1,
}).Error)
}
}
func requireGroupByType(t *testing.T, groups []*pb.AssetGroup, assetType string) *pb.AssetGroup {
t.Helper()
for _, group := range groups {
if group.Type == assetType {
return group
}
}
t.Fatalf("group type %q not found", assetType)
return nil
}
func itemLikeCounts(items []*pb.AssetItem) []int32 {
counts := make([]int32, 0, len(items))
for _, item := range items {
counts = append(counts, item.LikeCount)
}
return counts
}
Add fmt to the existing import block.
- Step 2: Add the failing home-versus-More regression test
Append after TestGetAssetsByType_FilterByCategory:
func TestStarbookHomePreviewAndMoreUseAssetLikeRanking(t *testing.T) {
db := starbookTestDB(t)
sbSeed(t, db)
sbAddRankedCollectionAssets(t, db)
provider := sbProvider(db)
ctx := sbCtx(sbTestOwnerUID, sbTestStarID)
homeResp, err := provider.GetMyAssets(ctx, &pb.GetMyAssetsRequest{})
require.NoError(t, err)
require.NotNil(t, homeResp.Data)
homeGroup := requireGroupByType(t, homeResp.Data.Groups, models.AssetTypeCollection)
assert.Equal(t, []int32{40, 30, 20}, itemLikeCounts(homeGroup.Items))
assert.Equal(t, int32(4), homeGroup.TotalCount)
assert.True(t, homeGroup.HasMore)
moreResp, err := provider.GetAssetsByType(ctx, &pb.GetAssetsByTypeRequest{
Type: models.AssetTypeCollection,
Category: "手办",
})
require.NoError(t, err)
require.NotNil(t, moreResp.Data)
moreGroup := requireGroupByType(t, moreResp.Data.Groups, models.AssetTypeCollection)
assert.Equal(t, []int32{40, 30, 20, 5}, itemLikeCounts(moreGroup.Items))
assert.Equal(t, int32(4), moreGroup.TotalCount)
assert.False(t, moreGroup.HasMore)
}
- Step 3: Prove the integration test would catch reversed call-site limits
Before accepting GREEN, temporarily change only the GetMyAssets collection call from homePreviewLimit to 0, then run:
go -C backend/services/assetService test -count=1 ./provider -run '^TestStarbookHomePreviewAndMoreUseAssetLikeRanking$' -v
Expected: FAIL because home returns four items instead of [40, 30, 20] and HasMore is false. Restore homePreviewLimit immediately after observing the expected failure.
- Step 4: Run the restored integration test and verify GREEN
Run:
gofmt -w backend/services/assetService/provider/asset_provider_test.go
go -C backend/services/assetService test -count=1 ./provider -run 'Test(GetAssetsByType|StarbookHomePreviewAndMore)' -v
Expected: existing filter/auth tests and the new home/More ranking test pass. If PostgreSQL is unavailable, the DB-backed tests explicitly report SKIP; the DB-free Task 1/2 tests must still pass and are not optional.
- Step 5: Review checkpoint
Run:
git diff --check -- backend/services/assetService/provider/asset_provider_test.go
git diff -- backend/services/assetService/provider/asset_provider_test.go
Confirm the fixture manually specifies no IDs, so the PostgreSQL sequence rule is not newly triggered. Existing sbSeed manual user/star IDs retain their existing setval calls. Do not commit.
Task 4: Full regression and cross-impact verification
Files:
- Verify only; no planned source changes.
Interfaces:
-
Verifies all outputs from Tasks 1-3.
-
Step 1: Run the complete DB-free group regression
Run:
go -C backend/services/assetService test -count=1 ./service -run 'TestBuild(Regular|Collection|Activity)GroupForAssets' -v
Expected: all group-builder tests pass.
- Step 2: Run starbook provider and gateway tests
Run:
go -C backend/services/assetService test -count=1 ./provider -run 'Test(GetAssetsByType|StarbookHomePreviewAndMore)' -v
go -C backend/gateway test -count=1 ./controller -run '^TestStarbook' -v
Expected: all selected tests pass, or only DB-backed provider tests explicitly SKIP because PostgreSQL is unavailable. No selected test may fail.
- Step 3: Compile gateway and assetService
Run:
go -C backend/gateway build ./...
go -C backend/services/assetService build ./...
Expected: both commands exit 0.
- Step 4: Precompile every service from
dev.shwithout overwriting repository binaries
Run:
tmp=$(mktemp -d /tmp/topfans-starbook-build.XXXXXX)
trap 'rm -rf "$tmp"' EXIT
for spec in \
'gateway:gateway' \
'userService:services/userService' \
'assetService:services/assetService' \
'socialService:services/socialService' \
'galleryService:services/galleryService' \
'activityService:services/activityService' \
'taskService:services/taskService' \
'aiChatService:services/aiChatService' \
'statisticService:services/statisticService' \
'notificationService:services/notificationService' \
'moderationService:services/moderationService'; do
name=${spec%%:*}
dir=${spec#*:}
go -C "backend/$dir" build -o "$tmp/$name" . || exit 1
done
Expected: all 11 builds exit 0.
- Step 5: Re-run graph cross-impact checks
Using code-review-graph:
- Incrementally update the graph for
backend/services/assetService/service/asset_service.goand the two test files. - Query
callers_offor all threebuild*GroupForAssetsfunctions. - Verify
GetMyAssetspasseshomePreviewLimitandGetAssetsByTypepasses0for regular, collection, and activity. - Query
tests_forforGetMyAssetsandGetAssetsByType; record any graph limitation if the new tests are not linked automatically.
Expected: no stale three-argument builder caller remains.
- Step 6: Perform the mandatory global self-review
Check every source requirement against the diff:
[ ] regular uses Asset.LikeCount DESC, Asset.ID ASC
[ ] collection uses Asset.LikeCount DESC, Asset.ID ASC
[ ] activity uses Asset.LikeCount DESC, Asset.ID ASC
[ ] home returns at most 3 per regular grade / collection group / activity group
[ ] home has_more is true only when valid items were omitted
[ ] More returns all valid items in the same order
[ ] dangling registries do not affect total_count or has_more
[ ] frontend and protobuf files are unchanged by this feature
[ ] existing unrelated working-tree changes remain intact
Run:
git diff --check
git status --short
git diff --stat
Expected: no whitespace errors; only intended feature files plus the pre-existing protobuf/gateway changes and design/plan documents are present. Do not commit or push.
Plan Self-Review
Spec coverage
- Home limit 3: Tasks 1-2 implement; Task 3 verifies through
GetMyAssets. - Highest likes first for all types: shared helper in Task 1, all builders in Tasks 1-2.
- Authoritative source is
Asset.LikeCount: fixture conflicts with registry likes in Tasks 1 and 3. - Stable tie ordering
Asset.ID ASC: regular test IDs 2 and 4 both have 30 likes; expected order proves the tie rule. has_more: builder tests cover preview and unlimited modes; integration verifies serialization DTO values before gateway JSON.- More returns all: Tasks 1-2 unit tests plus Task 3 end-to-end test.
- Invalid registry exclusion: every builder test includes asset ID 999 without a matching asset.
- No frontend/proto changes: enforced in global constraints and Task 4 review.
- Regression: targeted tests, two builds, all-service precompile, graph checks, and global self-review are explicit.
Placeholder scan
The plan contains no TBD, TODO, “implement later,” unspecified test request, or undefined interface. Every code-changing step includes concrete code and an exact verification command.
Type consistency
LikeCountisint32inmodels.Asset,models.AssetRegistry, andpb.AssetItem.Asset.IDandpb.AssetItem.AssetIdareint64.previewLimitis consistentlyint.- The helper returns
([]*pb.AssetItem, bool)and every builder consumes both return values. TotalCountremains protobuf-compatibleint32.