Skip to content

feat: unhide topic feed page + redesign input source editor - #783

Merged
Colin-XKL merged 3 commits into
devfrom
cursor/unhide-topic-feed-aeca
May 24, 2026
Merged

feat: unhide topic feed page + redesign input source editor#783
Colin-XKL merged 3 commits into
devfrom
cursor/unhide-topic-feed-aeca

Conversation

@Colin-XKL

@Colin-XKL Colin-XKL commented May 23, 2026

Copy link
Copy Markdown
Owner

变更说明

1. 取消隐藏 Topic Feed 页面

  • 取消 worktable.ts 中两条路由的注释(TopicFeedTopicFeedDetail
  • 恢复 Observability 页面中「查看详情」按钮的 v-if 条件(移除 false &&

2. 重设计输入源编辑器

每条输入源行现在包含三个模式:

类型 交互 存储格式
外部 RSS 文本输入框(http/https URL) 原始 URL
Recipe 下拉选择(从 /api/admin/recipes 加载) feedcraft://recipe/:id
Topic 下拉选择(从 /api/admin/topics 加载,排除自身) feedcraft://topic/:id
  • 打开弹窗时自动加载 Recipe 和 Topic 列表(非阻塞加载,失败时优雅降级)
  • 编辑已有主题时,自动解析现有 feedcraft:// URI 并回填到对应类型+选择器
  • Recipe / Topic 下拉支持搜索和清除

3. 布局优化

  • a-divider 将表单分为「输入源」/「聚合规则」两个视觉区段
  • 输入源行采用 CSS Grid (auto 1fr auto) 布局,确保输入/选择控件填充剩余宽度

测试结果

topic_feed_input_redesign_test.mp4

新版输入源行:外部RSS 模式,带完整宽度输入框
切换为 Recipe 类型时的下拉选择器
创建成功后的列表页

  • ✅ 三种输入源类型全部可用(外部RSS / Recipe / Topic)
  • ✅ 创建 / 编辑 / 保存 / 删除全流程测试通过
  • ✅ 编辑时现有 URI 正确解析并回填到对应类型+选择器
  • task fixgo test ./...task frontend-build 全部通过

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

Summary by Sourcery

Expose the Topic Feed feature in the admin UI and redesign the topic input source editor to support typed sources with better layout and selection UX.

New Features:

  • Re-enable navigation and detail routes for Topic Feed in the worktable section and from the observability page for topic resources.
  • Introduce a typed input source model for topics that supports external RSS URLs, custom recipes, and other topic feeds via dedicated controls.

Enhancements:

  • Revamp the topic feed editor layout by separating input sources and aggregation rules with dividers and using a grid layout for input rows to improve readability and alignment.
  • Add asynchronous loading and searchable dropdowns for selecting custom recipes and topic feeds, excluding the current topic when editing.
  • Improve localization strings for topic input sources to reflect the new source types and guidance for using them.

cursoragent and others added 3 commits May 23, 2026 15:31
- Uncomment topic_feed routes in worktable.ts (TopicFeed and TopicFeedDetail)
- Remove v-if="false" guard on topic detail button in observability page
- Remove stale development-status comments

Co-authored-by: Colin <Colin_XKL@outlook.com>
- Replace plain text inputs with type-aware source rows
- Each row has a segmented selector: External RSS / Recipe / Topic
- External RSS: free-form URL input (http/https)
- Recipe: searchable dropdown loading from /api/admin/recipes
- Topic: searchable dropdown loading from /api/admin/topics (excludes self when editing)
- Auto-parse existing feedcraft:// URIs into the correct type+id on edit
- Load picker data when modal opens (non-fatal fallback on error)
- Add section dividers for 'Input Sources' and 'Aggregation Rules'
- Update locale strings (zh-CN and en-US) for new UI elements

Co-authored-by: Colin <Colin_XKL@outlook.com>
- Use CSS grid (auto 1fr auto) for input-source-row layout
- Add width: 100% to ensure grid container fills parent flex item
- Remove wrapper div approach; input/select are now direct grid children
- Remove redundant size='small' from radio group (prevent text wrapping)
- Add :deep(.arco-radio-group) { white-space: nowrap } for button labels

Co-authored-by: Colin <Colin_XKL@outlook.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@sourcery-ai

sourcery-ai Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Re-enables the Topic Feed feature in the admin UI and redesigns the Topic Feed input source editor to support typed sources (external RSS, Recipe, Topic) with improved layout, while wiring these new UI models to existing topic feed APIs and observability navigation.

Sequence diagram for TopicFeed input source editor lifecycle

sequenceDiagram
  actor AdminUser
  participant TopicFeedPage
  participant loadPickerData
  participant getCustomRecipes
  participant listTopicFeeds
  participant parseUriToSource
  participant sourceToUri
  participant normalizeTopicPayload

  AdminUser->>TopicFeedPage: openTopicFeedPage
  TopicFeedPage->>loadPickerData: loadPickerData
  loadPickerData->>getCustomRecipes: getCustomRecipes
  loadPickerData->>listTopicFeeds: listTopicFeeds
  getCustomRecipes-->>loadPickerData: recipesRes
  listTopicFeeds-->>loadPickerData: topicsRes
  loadPickerData-->>TopicFeedPage: pickerDataLoaded

  alt editing_existing_topic
    TopicFeedPage->>parseUriToSource: record.input_uris.map(parseUriToSource)
    parseUriToSource-->>TopicFeedPage: InputSourceItem[]
  end

  AdminUser->>TopicFeedPage: submitTopicChanges
  TopicFeedPage->>sourceToUri: formData.inputSources.map(sourceToUri)
  sourceToUri-->>TopicFeedPage: string[]
  TopicFeedPage->>normalizeTopicPayload: normalizeTopicPayload
  normalizeTopicPayload-->>TopicFeedPage: TopicFeed
  Note over TopicFeedPage: TopicFeed payload is then submitted via existing API call
Loading

Flow diagram for Observability navigation to TopicFeedDetail route

flowchart LR
  AdminUser([Admin User]) --> ObservabilityPage
  ObservabilityPage -->|resource_type === topic| goToTopicDetail
  goToTopicDetail -->|router.push| TopicFeedDetailRoute[TopicFeedDetail route]
  TopicFeedDetailRoute --> TopicFeedDetailPage[TopicFeed detail view]
Loading

File-Level Changes

Change Details Files
Redesigned topic feed input editor to use typed input sources with per-type controls and URI mapping.
  • Replace flat string array input_uris in the form with structured inputSources items that track sourceType, externalUrl, and resourceId for each row.
  • Add radio-group per row to choose between External RSS, Recipe, or Topic, conditionally rendering either a URL input or searchable dropdowns populated from recipes and topics APIs.
  • Implement helper functions to map stored feedcraft://recipe/:id and feedcraft://topic/:id URIs to/from the new InputSourceItem model, and update payload normalization to emit input_uris for the backend.
  • Ensure editing an existing topic parses existing URIs into the new structure, and preserve at least one row when rows are removed.
web/admin/src/views/dashboard/topic_feed/topic_feed.vue
Load and expose recipe/topic data for the new pickers, including edit-mode topic filtering.
  • Import and use getCustomRecipes plus existing listTopicFeeds to fetch available recipes and topics when the modal opens, tracking loading state and gracefully ignoring failures.
  • Expose computed pickerTopics list that filters out the current topic when editing to avoid self-referential topic inputs.
web/admin/src/views/dashboard/topic_feed/topic_feed.vue
web/admin/src/api/custom_recipe.ts (existing import usage)
Improve Topic Feed form layout and styling for input sources and aggregator rules.
  • Insert labeled dividers to visually separate the Input Sources and Aggregation Rules sections instead of single labeled form items.
  • Define a CSS grid layout for input source rows so the selector/input expands to fill remaining width, and add styles for option id/description text in dropdowns.
web/admin/src/views/dashboard/topic_feed/topic_feed.vue
Update i18n strings to reflect the new input model and labels for Topic feeds.
  • Adjust helper text to describe external RSS vs Recipe/Topic dropdown usage instead of raw feedcraft:// URIs.
  • Add new localized keys for section titles, source type labels, URL placeholder, and dropdown placeholders in both English and Chinese topic locale files.
web/admin/src/locale/en-US/topic.ts
web/admin/src/locale/zh-CN/topic.ts
Unhide Topic Feed feature in routing and observability UI.
  • Re-enable TopicFeed and TopicFeedDetail child routes under the worktable module so the Topic Feed page and detail view are navigable.
  • Restore the Observability table’s "View Detail" button for topic resources by removing the hardcoded false guard in its v-if and keep the router navigation helper.
  • Remove outdated comments indicating Topic Feed is hidden/unfinished.
web/admin/src/router/routes/modules/worktable.ts
web/admin/src/views/dashboard/observability/index.vue

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@vercel

vercel Bot commented May 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
feed-craft-admin Ready Ready Preview, Comment May 23, 2026 5:41pm
feed-craft-doc Ready Ready Preview, Comment May 23, 2026 5:41pm

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In the template, the Recipe radio label is hard-coded while other labels use i18n keys; consider switching it to t('topic.sourceType.recipe') for consistency and localization support.
  • The logic for creating a default InputSourceItem (currently repeated in defaultFormData, addSource, and removeSource) could be extracted into a small helper to avoid duplication and keep future changes to the default shape in one place.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the template, the `Recipe` radio label is hard-coded while other labels use i18n keys; consider switching it to `t('topic.sourceType.recipe')` for consistency and localization support.
- The logic for creating a default `InputSourceItem` (currently repeated in `defaultFormData`, `addSource`, and `removeSource`) could be extracted into a small helper to avoid duplication and keep future changes to the default shape in one place.

## Individual Comments

### Comment 1
<location path="web/admin/src/views/dashboard/topic_feed/topic_feed.vue" line_range="450-452" />
<code_context>
-    input_uris: formData.value.input_uris
-      .map((item) => item.trim())
-      .filter((item) => item !== ''),
+    input_uris: formData.value.inputSources
+      .map(sourceToUri)
+      .filter((uri) => uri !== ''),
     aggregator_config: formData.value.aggregator_config.map((step) => {
       const option: Record<string, string> = {};
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid emitting recipe/topic URIs when no resource has been selected yet.

When `sourceType` is `recipe` or `topic` and `resourceId` is empty, `sourceToUri` returns `feedcraft://recipe/` or `feedcraft://topic/`, which passes the `uri !== ''` filter and is sent in the payload. This is likely an invalid URI and could confuse backend handling/validation. Please either have `sourceToUri` return `''` when `resourceId` is falsy for non-external types, or filter `inputSources` so only entries with a non-empty `externalUrl`/`resourceId` (per `sourceType`) are mapped, so incomplete UI rows don’t emit malformed URIs.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +450 to +452
input_uris: formData.value.inputSources
.map(sourceToUri)
.filter((uri) => uri !== ''),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Avoid emitting recipe/topic URIs when no resource has been selected yet.

When sourceType is recipe or topic and resourceId is empty, sourceToUri returns feedcraft://recipe/ or feedcraft://topic/, which passes the uri !== '' filter and is sent in the payload. This is likely an invalid URI and could confuse backend handling/validation. Please either have sourceToUri return '' when resourceId is falsy for non-external types, or filter inputSources so only entries with a non-empty externalUrl/resourceId (per sourceType) are mapped, so incomplete UI rows don’t emit malformed URIs.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity

Metric Results
Complexity 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enables the TopicFeed feature by activating its routes and UI entry points. It significantly improves the user experience in the Topic Feed editor by replacing manual URI inputs with a structured 'Input Sources' section, allowing users to select between External RSS, Recipes, or other Topics using dedicated controls. Review feedback identifies opportunities to enhance internationalization for new UI labels, ensure robust URI generation when selections are cleared, and improve the resilience of data fetching for the resource pickers by using Promise.allSettled.

Comment on lines +143 to +144
<a-radio value="recipe">Recipe</a-radio>
<a-radio value="topic">Topic</a-radio>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These radio button labels are hardcoded. They should use the internationalization keys defined in the locale files to maintain consistency and support multi-language support.

              <a-radio value="recipe">{{
                t('topic.sourceType.recipe')
              }}</a-radio>
              <a-radio value="topic">{{
                t('topic.sourceType.topic')
              }}</a-radio>

Comment on lines +413 to +421
const sourceToUri = (source: InputSourceItem): string => {
if (source.sourceType === 'recipe') {
return `feedcraft://recipe/${source.resourceId}`;
}
if (source.sourceType === 'topic') {
return `feedcraft://topic/${source.resourceId}`;
}
return source.externalUrl.trim();
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If a user selects 'Recipe' or 'Topic' but clears the selection (via allow-clear), resourceId will be an empty string. The current implementation would return feedcraft://recipe/ or feedcraft://topic/, which are invalid URIs. It's better to return an empty string in these cases so they can be filtered out during payload normalization.

  const sourceToUri = (source: InputSourceItem): string => {
    if (source.sourceType === 'recipe') {
      return source.resourceId ? `feedcraft://recipe/${source.resourceId}` : '';
    }
    if (source.sourceType === 'topic') {
      return source.resourceId ? `feedcraft://topic/${source.resourceId}` : '';
    }
    return source.externalUrl.trim();
  };

Comment on lines +423 to +437
const loadPickerData = async () => {
pickerLoading.value = true;
try {
const [recipesRes, topicsRes] = await Promise.all([
getCustomRecipes(),
listTopicFeeds(),
]);
availableRecipes.value = recipesRes.data ?? [];
availableTopics.value = topicsRes.data ?? [];
} catch {
// non-fatal: picker continues with empty lists
} finally {
pickerLoading.value = false;
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using Promise.all means that if either getCustomRecipes() or listTopicFeeds() fails, the entire data loading process fails, and both lists remain empty. Using Promise.allSettled allows each request to succeed or fail independently, ensuring that if one API is down, the other picker can still function.

  const loadPickerData = async () => {
    pickerLoading.value = true;
    try {
      const [recipesRes, topicsRes] = await Promise.allSettled([
        getCustomRecipes(),
        listTopicFeeds(),
      ]);
      if (recipesRes.status === 'fulfilled') {
        availableRecipes.value = recipesRes.value.data ?? [];
      }
      if (topicsRes.status === 'fulfilled') {
        availableTopics.value = topicsRes.value.data ?? [];
      }
    } catch {
      // non-fatal
    } finally {
      pickerLoading.value = false;
    }
  };

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Topic feed feature is enabled by re-activating routes and updating observability dashboard visibility. The topic input editor is refactored from flat input URIs to a structured model supporting external URLs, custom recipes, and internal topics, with new locale strings, type definitions, and helpers for parsing and serializing between formats.

Changes

Topic Feed Feature Unhiding and Input Source Redesign

Layer / File(s) Summary
Locale strings and routing configuration
web/admin/src/locale/en-US/topic.ts, web/admin/src/locale/zh-CN/topic.ts, web/admin/src/router/routes/modules/worktable.ts, web/admin/src/views/dashboard/observability/index.vue
English and Chinese locale strings updated with new keys for input source types (external, recipe, topic) and section headers; topic_feed and topic_feed/:id routes re-enabled in router; TopicFeed action in observability dashboard made conditionally visible for resource_type === 'topic'.
Form data structure and type definitions
web/admin/src/views/dashboard/topic_feed/topic_feed.vue
Component imports extended with computed and custom recipe API; SourceType and InputSourceItem types introduced to represent structured input sources; TopicFormData migrated to use inputSources: InputSourceItem[] instead of input_uris: string[]; reactive state added for availableRecipes, availableTopics, and pickerLoading.
Input source logic: parsing, loading, and normalization
web/admin/src/views/dashboard/topic_feed/topic_feed.vue
URI parsing/serialization helpers (parseUriToSource, sourceToUri) convert between flat input URIs and structured input sources; loadPickerData() fetches custom recipes and available topics; normalizeTopicPayload() generates input_uris from inputSources by translating recipe/topic sources to feedcraft:// URIs; form population in edit mode derives inputSources from record.input_uris; helper functions (addSource, removeSource, resetSourceValue) manage inputSources array with minimum-one-row guarantee.
Input sources template and styling
web/admin/src/views/dashboard/topic_feed/topic_feed.vue
Template replaced to render input sources list with per-row radio selection for source type, conditional controls (URL input for external, dropdown pickers for recipe/topic), and add/remove buttons; new section dividers for "Inputs" and "Aggregator"; scoped CSS added for .input-source-row layout and .option-id/.option-desc picker label styling.

Possibly Related PRs

  • Colin-XKL/FeedCraft#782: Re-enabled the same topic_feed routes and observability dashboard TopicFeed navigation changes that unhide the feature.
  • Colin-XKL/FeedCraft#780: Backend work to validate and generate FeedViewer previews from feedcraft:// URIs, which the refactored input editor now generates for recipe and topic sources.

🐰 Hops with joy—the topic feed emerges from development!
Three source types dance: external URLs, recipes, and topics,
Each with pickers and helpers to parse and persist.
The modal now loads and renders this structured delight,
Ready to feed the feeders with aggregated might! 🌾

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main changes: unhiding the topic feed page and redesigning the input source editor, matching the changeset scope.
Description check ✅ Passed The description provides comprehensive detail about the changes, including implementation specifics, testing results, and references to supporting artifacts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/unhide-topic-feed-aeca

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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 `@web/admin/src/views/dashboard/topic_feed/topic_feed.vue`:
- Around line 413-421: The sourceToUri function is generating malformed internal
URIs when resourceId is empty (e.g., producing "feedcraft://recipe/"), so update
sourceToUri to validate resourceId before building internal URIs: for
source.sourceType === 'recipe' or 'topic' check that source.resourceId is
non-empty (after trimming) and only then return
`feedcraft://recipe/${resourceId}` or `feedcraft://topic/${resourceId}`;
otherwise return an empty string (keep using source.externalUrl.trim() for
external sources) so the existing uri !== '' filter will correctly block
empty/invalid URIs. Ensure you reference and use sourceToUri, source.sourceType,
and source.resourceId in the change.
- Around line 423-433: loadPickerData currently uses Promise.all so a failure in
getCustomRecipes or listTopicFeeds cancels both results; instead call the two
APIs independently and handle failures separately: call getCustomRecipes() in
its own try/catch to set availableRecipes.value (default to []) and call
listTopicFeeds() in its own try/catch to set availableTopics.value (default to
[]), keep pickerLoading.value toggling (set true at start and false in finally),
and preserve non-fatal behavior so one endpoint failing doesn't wipe the other's
data; refer to loadPickerData, getCustomRecipes, listTopicFeeds,
availableRecipes, availableTopics, and pickerLoading.value when making the
changes.
- Around line 143-144: Replace the hardcoded English labels inside the a-radio
elements with the corresponding i18n translations (use the Vue $t call in the
template) so the UI uses the new locale keys; e.g., change <a-radio
value="recipe">Recipe</a-radio> and <a-radio value="topic">Topic</a-radio> to
use {{$t('...')}} (the existing locale keys for recipe and topic) while keeping
the value attributes intact so the selection logic (the a-radio group) continues
to work.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d2b35a1d-7b58-43b3-8873-145a115418e1

📥 Commits

Reviewing files that changed from the base of the PR and between 698e779 and 6b4a3e2.

📒 Files selected for processing (5)
  • web/admin/src/locale/en-US/topic.ts
  • web/admin/src/locale/zh-CN/topic.ts
  • web/admin/src/router/routes/modules/worktable.ts
  • web/admin/src/views/dashboard/observability/index.vue
  • web/admin/src/views/dashboard/topic_feed/topic_feed.vue

Comment on lines +143 to +144
<a-radio value="recipe">Recipe</a-radio>
<a-radio value="topic">Topic</a-radio>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize source-type labels instead of hardcoding English text.

The new locale keys exist, but Line 143 and Line 144 still render hardcoded labels, so Chinese UI won’t fully localize here.

💡 Suggested fix
-              <a-radio value="recipe">Recipe</a-radio>
-              <a-radio value="topic">Topic</a-radio>
+              <a-radio value="recipe">{{
+                t('topic.sourceType.recipe')
+              }}</a-radio>
+              <a-radio value="topic">{{
+                t('topic.sourceType.topic')
+              }}</a-radio>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a-radio value="recipe">Recipe</a-radio>
<a-radio value="topic">Topic</a-radio>
<a-radio value="recipe">{{
t('topic.sourceType.recipe')
}}</a-radio>
<a-radio value="topic">{{
t('topic.sourceType.topic')
}}</a-radio>
🤖 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 `@web/admin/src/views/dashboard/topic_feed/topic_feed.vue` around lines 143 -
144, Replace the hardcoded English labels inside the a-radio elements with the
corresponding i18n translations (use the Vue $t call in the template) so the UI
uses the new locale keys; e.g., change <a-radio value="recipe">Recipe</a-radio>
and <a-radio value="topic">Topic</a-radio> to use {{$t('...')}} (the existing
locale keys for recipe and topic) while keeping the value attributes intact so
the selection logic (the a-radio group) continues to work.

Comment on lines +413 to +421
const sourceToUri = (source: InputSourceItem): string => {
if (source.sourceType === 'recipe') {
return `feedcraft://recipe/${source.resourceId}`;
}
if (source.sourceType === 'topic') {
return `feedcraft://topic/${source.resourceId}`;
}
return source.externalUrl.trim();
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid emitting malformed internal URIs for empty source selections.

sourceToUri currently serializes empty resourceId values to feedcraft://recipe/ or feedcraft://topic/, which then bypasses the uri !== '' filter and reaches validation/save paths.

💡 Suggested fix
   const sourceToUri = (source: InputSourceItem): string => {
+    const id = source.resourceId?.trim();
     if (source.sourceType === 'recipe') {
-      return `feedcraft://recipe/${source.resourceId}`;
+      return id ? `feedcraft://recipe/${id}` : '';
     }
     if (source.sourceType === 'topic') {
-      return `feedcraft://topic/${source.resourceId}`;
+      return id ? `feedcraft://topic/${id}` : '';
     }
     return source.externalUrl.trim();
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const sourceToUri = (source: InputSourceItem): string => {
if (source.sourceType === 'recipe') {
return `feedcraft://recipe/${source.resourceId}`;
}
if (source.sourceType === 'topic') {
return `feedcraft://topic/${source.resourceId}`;
}
return source.externalUrl.trim();
};
const sourceToUri = (source: InputSourceItem): string => {
const id = source.resourceId?.trim();
if (source.sourceType === 'recipe') {
return id ? `feedcraft://recipe/${id}` : '';
}
if (source.sourceType === 'topic') {
return id ? `feedcraft://topic/${id}` : '';
}
return source.externalUrl.trim();
};
🤖 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 `@web/admin/src/views/dashboard/topic_feed/topic_feed.vue` around lines 413 -
421, The sourceToUri function is generating malformed internal URIs when
resourceId is empty (e.g., producing "feedcraft://recipe/"), so update
sourceToUri to validate resourceId before building internal URIs: for
source.sourceType === 'recipe' or 'topic' check that source.resourceId is
non-empty (after trimming) and only then return
`feedcraft://recipe/${resourceId}` or `feedcraft://topic/${resourceId}`;
otherwise return an empty string (keep using source.externalUrl.trim() for
external sources) so the existing uri !== '' filter will correctly block
empty/invalid URIs. Ensure you reference and use sourceToUri, source.sourceType,
and source.resourceId in the change.

Comment on lines +423 to +433
const loadPickerData = async () => {
pickerLoading.value = true;
try {
const [recipesRes, topicsRes] = await Promise.all([
getCustomRecipes(),
listTopicFeeds(),
]);
availableRecipes.value = recipesRes.data ?? [];
availableTopics.value = topicsRes.data ?? [];
} catch {
// non-fatal: picker continues with empty lists

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Load recipe/topic pickers independently to preserve partial success.

Using Promise.all means one failed request drops both picker datasets. That degrades edit UX when only one endpoint is temporarily failing.

💡 Suggested fix
   const loadPickerData = async () => {
     pickerLoading.value = true;
     try {
-      const [recipesRes, topicsRes] = await Promise.all([
+      const [recipesRes, topicsRes] = await Promise.allSettled([
         getCustomRecipes(),
         listTopicFeeds(),
       ]);
-      availableRecipes.value = recipesRes.data ?? [];
-      availableTopics.value = topicsRes.data ?? [];
+      availableRecipes.value =
+        recipesRes.status === 'fulfilled' ? recipesRes.value.data ?? [] : [];
+      availableTopics.value =
+        topicsRes.status === 'fulfilled' ? topicsRes.value.data ?? [] : [];
     } catch {
       // non-fatal: picker continues with empty lists
     } finally {
       pickerLoading.value = false;
     }
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const loadPickerData = async () => {
pickerLoading.value = true;
try {
const [recipesRes, topicsRes] = await Promise.all([
getCustomRecipes(),
listTopicFeeds(),
]);
availableRecipes.value = recipesRes.data ?? [];
availableTopics.value = topicsRes.data ?? [];
} catch {
// non-fatal: picker continues with empty lists
const loadPickerData = async () => {
pickerLoading.value = true;
try {
const [recipesRes, topicsRes] = await Promise.allSettled([
getCustomRecipes(),
listTopicFeeds(),
]);
availableRecipes.value =
recipesRes.status === 'fulfilled' ? recipesRes.value.data ?? [] : [];
availableTopics.value =
topicsRes.status === 'fulfilled' ? topicsRes.value.data ?? [] : [];
} catch {
// non-fatal: picker continues with empty lists
} finally {
pickerLoading.value = false;
}
};
🤖 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 `@web/admin/src/views/dashboard/topic_feed/topic_feed.vue` around lines 423 -
433, loadPickerData currently uses Promise.all so a failure in getCustomRecipes
or listTopicFeeds cancels both results; instead call the two APIs independently
and handle failures separately: call getCustomRecipes() in its own try/catch to
set availableRecipes.value (default to []) and call listTopicFeeds() in its own
try/catch to set availableTopics.value (default to []), keep pickerLoading.value
toggling (set true at start and false in finally), and preserve non-fatal
behavior so one endpoint failing doesn't wipe the other's data; refer to
loadPickerData, getCustomRecipes, listTopicFeeds, availableRecipes,
availableTopics, and pickerLoading.value when making the changes.

@Colin-XKL
Colin-XKL merged commit e64ac9d into dev May 24, 2026
10 checks passed
@Colin-XKL
Colin-XKL deleted the cursor/unhide-topic-feed-aeca branch May 24, 2026 03:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants