Polish craft admin pages#839
Conversation
Updates the markdown documentation to reflect code changes from the last 7 days: - Updated tools docs to include the new Missing Crafts Panel for System Health (24bf35a) - Updated system-craft-atoms docs to reflect `guid-fix` using FNV-1a hash instead of MD5 (c7e60a4) Co-authored-by: Colin-XKL <49122401+Colin-XKL@users.noreply.github.com>
* fix: correctly handle logout action and provide feedback - Update `web/admin/src/api/user.ts` to use a mock promise for logout API, as the backend uses stateless JWT and does not provide a logout endpoint (preventing a 404 error). - Add direct logout button to the top navigation bar right menu in `web/admin/src/components/navbar/index.vue` to make it discoverable. - Enhance the logout feedback by modifying the useUser hook to accept a success message and mapping proper i18n locales for the toast notification. Co-authored-by: Colin-XKL <49122401+Colin-XKL@users.noreply.github.com> * fix: correctly handle logout action and provide feedback - Update `web/admin/src/api/user.ts` to use a mock promise for logout API, as the backend uses stateless JWT and does not provide a logout endpoint (preventing a 404 error). - Add direct logout button to the top navigation bar right menu in `web/admin/src/components/navbar/index.vue` to make it discoverable. - Enhance the logout feedback by modifying the useUser hook to accept a success message and mapping proper i18n locales for the toast notification. Co-authored-by: Colin-XKL <49122401+Colin-XKL@users.noreply.github.com> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Allow users to preview recipes directly from the table - Enable quick copy of recipe links for sharing - Clean up UI by removing duplicate buttons and updating column header
- Introduced WebMonitorParserConfig to capture extractors, key fields, and templates - Added SourceWebMonitor type and factory to register pipeline with HttpFetcher and WebMonitorParser - Implemented WebMonitorParser with parsing, preview, and GUID generation logic - Created preview endpoint `/tools/web-monitor/preview` and updated router registry - Updated SourceConfig to include WebMonitorParser field and adjusted HTML/JSON parser handling - Added constants for new source type and updated documentation comments - Added unit tests for parser behavior, key field handling, and factory defaults - Updated source factory tests to verify default fetcher purpose for web monitor sources.
Replaced the invalid JSON placeholder `"{ 'foo': 'bar' }"` with the valid `{"foo": "bar"}` wrapped in single quotes in both the English (`en-US`) and Chinese (`zh-CN`) locale files (`jsonToRss.ts`).
Co-authored-by: Colin-XKL <49122401+Colin-XKL@users.noreply.github.com>
…faultClient Replaces the usage of `http.DefaultClient` with a dedicated `http.Client` in `HttpFetcher` to prevent unbounded requests. Adds `HttpClientTimeout` to `HttpFetcherConfig` to allow configuring the timeout behavior, falling back to a sensible default of 30 seconds. Co-authored-by: Colin-XKL <49122401+Colin-XKL@users.noreply.github.com>
Co-authored-by: Colin <Colin_XKL@outlook.com>
* feat: add legacy ai filter atom craft Co-authored-by: Colin <Colin_XKL@outlook.com> * test: make ai filter summary assertions deterministic Co-authored-by: Colin <Colin_XKL@outlook.com> * test: initialize redis for advertorial processor error case Co-authored-by: Colin <Colin_XKL@outlook.com> * chore: satisfy ai filter lint suggestions Co-authored-by: Colin <Colin_XKL@outlook.com> * chore: finish ai filter lint cleanup Co-authored-by: Colin <Colin_XKL@outlook.com> * test: assert ai filter uses legacy adapter Co-authored-by: Colin <Colin_XKL@outlook.com> * test: match ai filter legacy processor wrapping Co-authored-by: Colin <Colin_XKL@outlook.com> * refactor: rename ai filter rule parameter Co-authored-by: Colin <Colin_XKL@outlook.com> * feat: cache ai filter decisions Co-authored-by: Colin <Colin_XKL@outlook.com> * feat: add ai filter extra payload multiselect Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat: add legacy ai filter atom craft Co-authored-by: Colin <Colin_XKL@outlook.com> * test: make ai filter summary assertions deterministic Co-authored-by: Colin <Colin_XKL@outlook.com> * test: initialize redis for advertorial processor error case Co-authored-by: Colin <Colin_XKL@outlook.com> * chore: satisfy ai filter lint suggestions Co-authored-by: Colin <Colin_XKL@outlook.com> * chore: finish ai filter lint cleanup Co-authored-by: Colin <Colin_XKL@outlook.com> * test: assert ai filter uses legacy adapter Co-authored-by: Colin <Colin_XKL@outlook.com> * test: match ai filter legacy processor wrapping Co-authored-by: Colin <Colin_XKL@outlook.com> * refactor: rename ai filter rule parameter Co-authored-by: Colin <Colin_XKL@outlook.com> * feat: cache ai filter decisions Co-authored-by: Colin <Colin_XKL@outlook.com> * feat: add ai filter extra payload multiselect Co-authored-by: Colin <Colin_XKL@outlook.com> * docs: add development environment guide Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Colin <Colin_XKL@outlook.com>
* feat: add example rss feeds Co-authored-by: Colin <Colin_XKL@outlook.com> * style: auto-format code * fix: open example rss feeds directly Co-authored-by: Colin <Colin_XKL@outlook.com> * fix: use absolute URLs in example feeds Co-authored-by: Colin <Colin_XKL@outlook.com> * feat: add feed format examples Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* feat: add rss format probes Co-authored-by: Colin <Colin_XKL@outlook.com> * fix: remove rss 0.92 example feed Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Added a sponsorship section to acknowledge project sponsors.
* feat: 新增基于 Embedding 的主题过滤器及多后端支持 * chore: 在.gitignore中添加db/目录忽略规则 * chore: 新增忽略 start.sh 与 start-full.sh 文件 * refactor: 优化countMissing可读性与新增Vite代理路径 * chore: 删除 task-item.md 实施计划文件 * chore: 更新.gitignore忽略.codebuddy目录并清理旧项 * refactor: 重构embedding模块错误处理与UTF-8安全截断 * feat: 增强embedding模块功能与测试覆盖 * feat: 拆分embedding批处理并加重试日志与结果合并 * feat: 支持自定义embedding批处理大小配置 * feat: 为 embedding-filter 新增 include 和 exclude 过滤模式 --------- Co-authored-by: gifwang <gifwang@tencent.com> Co-authored-by: Colin <Colin_XKL@outlook.com>
Co-authored-by: Colin <Colin_XKL@outlook.com>
Co-authored-by: Colin <Colin_XKL@outlook.com>
Co-authored-by: Colin <Colin_XKL@outlook.com>
Co-authored-by: Colin <Colin_XKL@outlook.com>
* Improve admin URL input layouts Co-authored-by: Colin <Colin_XKL@outlook.com> * Fix admin component tag order Co-authored-by: Colin <Colin_XKL@outlook.com> * Use Tailwind utilities for admin tool layouts Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix quick start copy url fallback Co-authored-by: Colin <Colin_XKL@outlook.com> * improve quick start generated url state Co-authored-by: Colin <Colin_XKL@outlook.com> * remove temporary quick start regression script Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Improve LLM retry model failover Co-authored-by: Colin <Colin_XKL@outlook.com> * Refine LLM retry rotation behavior Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat: add AI content process craft Co-authored-by: Colin <Colin_XKL@outlook.com> * fix: harden AI content process caching Co-authored-by: Colin <Colin_XKL@outlook.com> * fix: avoid empty content separators Co-authored-by: Colin <Colin_XKL@outlook.com> * fix: lower temperature for deterministic AI crafts Co-authored-by: Colin <Colin_XKL@outlook.com> * feat: hide template-only system crafts Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
No Custom Recipe required to subscribe to an inbox. Backend: - Add PublicInboxRSSFeed controller in inbox_public.go: loads inbox from DB, enforces public/private access (same token logic as article content), builds a CraftFeed from stored items, and renders RSS XML - Extract checkInboxAccess() helper to deduplicate auth logic shared by both PublicInboxItemContent and the new handler - Register GET /inbox/:inbox_id/rss in router Frontend: - Add directRssUrl computed property to inbox guide modal - Show direct RSS URL with copy button; private inbox hint with ?token= i18n (en-US / zh-CN / zh-TW): - inbox.guide.directRss.desc - inbox.guide.directRss.privateHint Docs (en / zh / zh-tw): - Restructure 'Subscribing via RSS' into two sections: 'Direct RSS URL (simplest)' and 'Via Custom Recipe (advanced)' Co-authored-by: Colin <Colin_XKL@outlook.com>
* feat: unhide topic feed page and re-enable detail navigation
- 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>
* feat: redesign topic feed input source editor with type picker
- 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>
* fix: resolve input field visibility and layout in topic feed editor
- 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>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Colin-XKL <49122401+Colin-XKL@users.noreply.github.com>
- Add dual-mode support to feed_viewer.vue: 'preview' (direct) and 'compare' (craft diff) - In compare mode, a CraftFlowSelect appears alongside the input for selecting a Craft - Compare mode shows side-by-side original vs. craft-processed feed panels - Remove standalone feed_compare.vue and its route from tools.ts - Update zh-CN and en-US locale files with new i18n keys; remove feedCompare menu entries - Backend and frontend builds pass; all Go tests pass Co-authored-by: Colin <Colin_XKL@outlook.com>
* fix(web-monitor): inject page URL into Parse(), add zh-CN locale, fix source_type dropdown
- Backend: add PageURL field to WebMonitorParser; Parse() now sets values["url"] = p.PageURL
so {{.url}} templates render correctly and GUID is URL-scoped in production
- Backend: webMonitorSourceFactory passes HttpFetcher.URL to WebMonitorParser.PageURL
- Tests: update existing test to assert URL is injected; add TestWebMonitorParser_Parse_URLInjectedIntoTemplateAndGUID
- Frontend: create web/admin/src/locale/zh-CN/webMonitor.ts with full Chinese translations
- Frontend: register localeWebMonitor in zh-CN.ts
- Frontend: add web_monitor option to source_type select in custom_recipe.vue
- Frontend: fix default content template in web_monitor.vue (remove non-existent {{.title}} var)
Co-authored-by: Colin <Colin_XKL@outlook.com>
* style: auto-format code
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* feat(topic): add by_title, by_simhash, by_embedding deduplication strategies
- by_title: exact title match deduplication
- by_simhash: character 2-gram SimHash with configurable Hamming distance threshold (default 3)
- pure Go implementation with no external dependencies
- strips HTML and normalizes text before hashing
- by_embedding: LLM Embedding cosine similarity with configurable threshold (default 0.9)
- reuses existing adapter.EmbedTexts and util.CosineSimilarity
- fails open (keeps all articles) when embedding service is unavailable
Frontend:
- added new strategy options to the deduplicate step selector
- added threshold number inputs for by_simhash (integer) and by_embedding (float)
- threshold resets to defaults when switching strategy
- updated formatAggregatorStep in detail.vue to show threshold values
- updated zh-CN and en-US locale files
Tests:
- TestDeduplicate_ByTitle
- TestDeduplicate_BySimhash_IdenticalContent
- TestDeduplicate_BySimhash_InvalidThreshold
- TestDeduplicate_BySimhash_DefaultThreshold
- TestDeduplicate_ByEmbedding_InvalidStrategy
- TestDeduplicate_ByEmbedding_ThresholdOutOfRange
Co-authored-by: Colin <Colin_XKL@outlook.com>
* feat(topic): normalize simhash threshold to 0-100 range
- User-facing threshold is now 0-100 (0=identical, 100=completely different)
- Backend converts to internal Hamming distance: hamming = round(threshold * 64 / 100)
- Default value changed from 3 (raw Hamming) to 5 (normalized, equivalent to hamming≈3)
- Validation updated: backend rejects values outside [0, 100]
- Frontend: input max changed from 64 to 100, default updated to 5
- Locale descriptions updated in zh-CN and en-US
- Tests updated with new range validation (0, 100 are valid; 101, -1, 999 are invalid)
Co-authored-by: Colin <Colin_XKL@outlook.com>
* feat(topic): change simhash threshold to float [0.0, 1.0] range
Align simhash threshold scale with by_embedding for consistent UX.
- Range: 0.0 (identical only) to 1.0 (everything is duplicate)
- Default: 0.05 (equivalent to hamming≈3, same behaviour as before)
- Internal conversion: hamming = round(threshold * 64)
- Frontend: step=0.01, precision=2, same as embedding input
- Tests updated for new float range
Co-authored-by: Colin <Colin_XKL@outlook.com>
* docs: update Topic Feed deduplication strategies in concepts (en/zh/zh-tw)
- Document 5 deduplication strategies: by_link, by_id, by_title, by_simhash, by_embedding
- Add threshold range and default value for by_simhash (0.0-1.0, default 0.05)
- Add threshold range and default value for by_embedding (0.0-1.0, default 0.9)
- Note fail-open behaviour for by_embedding when Embedding API is unavailable
- Add tip recommending Sort → Deduplicate → Limit pipeline order
- Remove stale caution about Topic Feed being hidden (feature is now available)
Co-authored-by: Colin <Colin_XKL@outlook.com>
* fix(topic): guard against out-of-bounds index in embedding dedup
EmbedTexts is expected to return a slice of the same length as the
input, but an unexpected short or nil result would cause a panic at
vectors[i]. Add two layers of protection:
1. Length check immediately after EmbedTexts: if len(vectors) !=
len(cloned.Articles), fail open and return all articles unchanged.
2. Defense-in-depth guard inside the loop: if i >= len(vectors) or
vectors[i] is empty, keep the article unconditionally.
Co-authored-by: Colin <Colin_XKL@outlook.com>
* feat(topic): improve SimHash/Embedding strategy descriptions in UI and docs
UI (locale zh-CN / en-US):
- Rename strategy options: 'Lexical Similarity (SimHash · Lightweight)' and
'Semantic Similarity (Embedding · Precise)'
- Add per-strategy hint text shown below the row when selected:
SimHash: character-based, no AI needed, best for reposts/rewrites
Embedding: semantic understanding, catches paraphrased duplicates,
requires Embedding API with fail-open fallback
- Improve threshold placeholders to clarify direction (lower=stricter vs
higher=stricter)
- Remove unused parseIntOption helper (SimHash now uses parseFloatOption)
Frontend template (topic_feed.vue):
- Wrap each aggregator step in .step-wrapper so hint text can appear
below the controls row without breaking the flex layout
- Add .step-hint paragraph rendered when by_simhash or by_embedding is
selected
Docs (en / zh / zh-tw):
- Expand by_simhash and by_embedding descriptions with lightweight vs.
precise contrast, use-case guidance, and threshold semantics
Co-authored-by: Colin <Colin_XKL@outlook.com>
* fix(topic): unify threshold semantics — both SimHash and Embedding use lower=stricter
Previously:
by_simhash threshold: difference tolerance, lower = stricter ✓
by_embedding threshold: cosine similarity floor, higher = stricter ✗ (opposite direction)
Now both use 'difference tolerance' in [0.0, 1.0], lower = stricter:
by_simhash: hamming <= round(threshold * 64) (unchanged)
by_embedding: cosine >= (1 - threshold) (flipped)
Default values:
by_simhash: 0.05 (unchanged, hamming ≈ 3)
by_embedding: 0.1 (was 0.9 cosine floor → same internal behaviour)
Updates:
- builder.go: flip cosine comparison to (1 - threshold), update default and comment
- builder_test.go: extend embedding out-of-range test to cover multiple bad values
- topic_feed.vue: defaultThreshold('by_embedding') 0.9 → 0.1
- locale zh-CN / en-US: both placeholder texts now say 'lower = stricter';
hint texts updated accordingly
- docs en / zh / zh-tw: both thresholds described as 'difference tolerance',
embedding default updated to 0.1
Co-authored-by: Colin <Colin_XKL@outlook.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…ators (#795) * style: optimize layout of topic creation/edition form inputs & aggregators Co-authored-by: Colin <Colin_XKL@outlook.com> * Delete docs/superpowers/specs/2026-05-29-optimize-topic-form-layout-design.md --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…t generator Co-authored-by: Colin <Colin_XKL@outlook.com>
- Change form items to vertical layout by separating search query and enhanced mode checkbox into distinct a-form-item components. - Improve spacing and alignment for checkbox description. - Increase prominence of the "Preview Results" button. - Replace fixed height with min-height for better responsiveness. Co-authored-by: Colin-XKL <49122401+Colin-XKL@users.noreply.github.com>
Converted all relative imports within the web/admin/src directory to use the @/ alias. This ensures consistency across the project and simplifies future refactoring. Verified that all tests pass after the changes. Co-authored-by: Colin-XKL <49122401+Colin-XKL@users.noreply.github.com>
- Removed hardcoded default embedding model 'text-embedding-3-small'. - Updated `loadEmbeddingConfig` to return an error if `FC_EMBEDDING_API_MODEL` is not set. - Updated `.env.example` and documentation (en, zh, zh-tw) to reflect that the model is now required. - Updated `internal/controller/dependency_monitor.go` to remove redundant default model logic. - Updated unit tests to provide explicit model names or expect errors when missing. Co-authored-by: Colin-XKL <49122401+Colin-XKL@users.noreply.github.com>
* feat: prevent editing AtomCraft name after creation Co-authored-by: Colin-XKL <49122401+Colin-XKL@users.noreply.github.com> * feat: enforce AtomCraft name immutability and refactor frontend edit logic - Added backend defense to reject AtomCraft name changes in UpdateCraftAtom. - Disabled name input field in frontend edit modal. - Refactored frontend logic to use 'originalName' ref for identifying resources during updates. - Added 'handleAdd' to properly reset form state when creating new AtomCrafts. - Restored accidentally deleted web/admin/package-lock.json. Co-authored-by: Colin-XKL <49122401+Colin-XKL@users.noreply.github.com> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
…king (#805) * feat: add optimistic caching for topic sub-feeds with per-URI health tracking When a sub-feed in a TopicFeed fails to fetch, the system now automatically falls back to the last cached result (stored up to 14 days in Redis) so the topic feed remains available despite partial upstream failures. Changes: - internal/constant/ttl.go: add SubFeedCacheTTL (14d) and SubFeedHealthTTL (30d) - internal/constant/cache_namespace.go: add PrefixSubFeedResult and PrefixSubFeedHealth - internal/feedruntime/cached_provider.go (new): CachedFeedProvider wraps any FeedProvider; on failure returns cached snapshot; health metadata (last success, last failure, last error, cached_at) persisted in Redis - internal/feedruntime/builder.go: wrap each topic input with CachedFeedProvider - internal/util/cache.go: add TryCacheSetString / TryCacheGetString that return errors instead of calling log.Fatalf when Redis is unavailable (test-safe) - internal/controller/topic_feed.go: add sub_feed_health to TopicDetailResponse - web/admin/src/api/topic.ts: add SubFeedHealth type; update TopicDetail - web/admin/src/views/dashboard/topic_feed/detail.vue: add sub-feed health card showing per-URI last-success, last-failure, error message, and cache status - web/admin/src/locale/zh-CN/topic.ts, en-US/topic.ts: add i18n keys - internal/feedruntime/builder_test.go: update nested-topic test for CachedFeedProvider Co-authored-by: Colin <Colin_XKL@outlook.com> * refactor: replace inline sha256 with util.GetMD5Hash in cached_provider Reuse the existing util.GetMD5Hash helper instead of importing crypto/sha256 directly. MD5 is sufficient for cache key derivation. Co-authored-by: Colin <Colin_XKL@outlook.com> * style: rename '子源' to '子 RSS 源' in zh-CN locale Co-authored-by: Colin <Colin_XKL@outlook.com> * refactor: unexport GetMD5Hash, unify external callers to GetTextContentHash - GetMD5Hash renamed to getMD5Hash (package-private) - GetPasswordMD5Hash is now the only exported MD5 entry point (security-focused) - adapter/embedding.go and feedruntime/cached_provider.go switched to GetTextContentHash (FNV-64a, efficiency-focused) for cache key derivation - hash_test.go updated to test via GetPasswordMD5Hash Co-authored-by: Colin <Colin_XKL@outlook.com> * docs(AGENTS.md): add hash utility guidelines Co-authored-by: Colin <Colin_XKL@outlook.com> * docs(AGENTS.md): remove mention of unexported getMD5Hash Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…se, and detail modal (#804) * feat(feed-viewer): enhance preview with HTML mode, card expand/collapse, and detail modal - Add raw HTML view mode with syntax highlighting (highlight.js) - Add HTML formatting utility (htmlFormat.ts) - Refactor FeedViewContainer into FeedItemCard + FeedItemContent + FeedItemDetailModal - FeedItemCard: collapsible card with title/date header and snippet preview - FeedItemContent: three view modes (normal/rich/html) with proper styling - FeedItemDetailModal: full-screen detail modal with switchable view modes - Fix rich text mode CSS (proper scoped deep styles for images, links, code blocks etc.) - Update i18n (zh-CN + en-US) with new feedViewer keys - All existing feed types (url/recipe/topic/inbox/uri) continue to work in compare mode Co-authored-by: Colin <Colin_XKL@outlook.com> * chore(deps): remove deprecated @types/highlight.js stub (highlight.js v11+ ships own types) Co-authored-by: Colin <Colin_XKL@outlook.com> * fix(htmlFormat): replace regex split with state-tracking scanner to handle '>' in quoted attributes Co-authored-by: Colin <Colin_XKL@outlook.com> * refactor(htmlFormat): replace custom parser with js-beautify Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(util): preserve paragraph line breaks in markdown/HTML conversion Single newlines in markdown were rendered as literal newlines inside <p> tags, which browsers collapse. HTML paragraphs with embedded newlines lost them when converted to markdown. Add post-processing to insert <br> for intentional line breaks inside <p> elements, apply it in both Markdown2HTML and Html2Markdown, deduplicate beautify conversion through util.Markdown2HTML, and add comprehensive tests. Co-authored-by: Colin <Colin_XKL@outlook.com> * refactor(util): treat single MD newlines as breaks and collapse blank runs FeedCraft mainly consumes LLM markdown and cleanup round-trips, where single newlines are intentional. Normalize input by collapsing consecutive blank lines, then promote remaining single newlines to hard breaks while skipping list boundaries, block starts, and fenced code. Also collapse blank runs in Html2Markdown output. Co-authored-by: Colin <Colin_XKL@outlook.com> * refactor(util): unify markdown conversion as MarkdownToHTML and HTMLToMarkdown Consolidate all MD↔HTML paths onto util.MarkdownToHTML and util.HTMLToMarkdown. Remove duplicate html-to-markdown usage in ProcessContent; rename md2html.go and update craft call sites (cleanup, beautify, LLM processors, etc.). Co-authored-by: Colin <Colin_XKL@outlook.com> * fix(util): avoid duplicate br when newline adjoins existing br tag Check br tag prefix on text after newline, not only suffix before it. Use compiled regexes for all <br> variants including attributes. Co-authored-by: Colin <Colin_XKL@outlook.com> * refactor(util): clean up hardcoded regex rules by using native gomarkdown extensions Instead of prepending spaces to newlines via string manipulation (which required custom regex to protect lists, quotes, tables, and code blocks), use the native parser.HardLineBreak extension in gomarkdown. Remove redundant regexes like blockStartRE and orderedListItemRE. Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat(topic-feed): wizard create/edit, input labels, and preview Replace the topic feed create/edit modal with a multi-step wizard page. Add per-input description fields persisted via inputs[] on the API. Extract FeedPreviewPanel for reuse when previewing sub-feeds in the editor. Co-authored-by: Colin <Colin_XKL@outlook.com> * feat(topic-feed): add disabled flag for sub-feed inputs Persist optional disabled on each input; runtime uses only enabled URIs. Detail page supports toggling disable without opening the full editor. Co-authored-by: Colin <Colin_XKL@outlook.com> * refactor(topic-feed): persist only structured inputs Co-authored-by: Colin <Colin_XKL@outlook.com> * refactor(topic-feed): clean redundant NormalizeInputs calls Remove GORM BeforeSave hook. Explicitly normalize inputs before Write/Update DB operations in Controller. Strip redundant read-path normalizations. Co-authored-by: Colin <Colin_XKL@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Colin <Colin_XKL@outlook.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| BestPractice | 5 minor |
| Security | 1 critical 1 high |
| Complexity | 39 medium |
🟢 Metrics 1555 complexity · 134 duplication
Metric Results Complexity 1555 Duplication 134
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.
There was a problem hiding this comment.
Code Review
This pull request introduces a new 'Inbox' feature for pushing and managing external RSS articles, along with a 'Web Monitor' feature for tracking specific webpage field changes. It also includes an 'Embedding Filter' for semantic topic filtering and various improvements to the internal runtime architecture, such as migrating to a functional CraftOption pattern. My review identified several critical issues: the Inbox GC logic causes data loss for 'no limit' configurations, the LLM adapter uses log.Fatalf which is too destructive, a previous SSRF protection check was accidentally removed, and the GUID generation uses a password-specific hash function instead of the recommended content hash. Additionally, I suggest adding ID validation to the Inbox update endpoint for consistency.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if maxItems <= 0 { | ||
| return db.Where("inbox_id = ?", inboxID).Delete(&InboxItem{}).Error | ||
| } |
There was a problem hiding this comment.
The current implementation where maxItems <= 0 deletes all items is highly unintuitive and poses a significant risk of accidental data loss. A value of 0 should typically mean 'no limit'. The documentation for this feature even warns about this behavior. To prevent destructive behavior, this logic should be changed so that maxItems <= 0 means no items are deleted.
| if maxItems <= 0 { | |
| return db.Where("inbox_id = ?", inboxID).Delete(&InboxItem{}).Error | |
| } | |
| if maxItems <= 0 { | |
| return nil | |
| } |
| if envClient == nil { | ||
| log.Fatalf("get env client error.") | ||
| } |
There was a problem hiding this comment.
Using log.Fatalf will terminate the entire application if the environment client fails to initialize. This is too severe for an adapter function. It's better to return an error and allow the caller to handle it gracefully, which makes the function more robust and the application more resilient.
| if envClient == nil { | |
| log.Fatalf("get env client error.") | |
| } | |
| if envClient == nil { | |
| return "", fmt.Errorf("get env client error") | |
| } |
| func validateFeedViewerURL(rawURL string) error { | ||
| parsedURL, err := url.Parse(rawURL) | ||
| if err != nil || parsedURL == nil { | ||
| return errors.New("Please enter a valid http(s) feed URL") | ||
| return errors.New(feedViewerInvalidURLError) | ||
| } | ||
| if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { | ||
| return errors.New("Please enter a valid http(s) feed URL") | ||
| return errors.New(feedViewerInvalidURLError) | ||
| } | ||
| if parsedURL.Hostname() == "" { | ||
| return errors.New("Please enter a valid http(s) feed URL") | ||
| return errors.New(feedViewerInvalidURLError) | ||
| } | ||
|
|
||
| ips, err := net.LookupIP(parsedURL.Hostname()) | ||
| if err != nil { | ||
| return fmt.Errorf("Unable to resolve this URL: %w", err) | ||
| return nil | ||
| } |
There was a problem hiding this comment.
The check for loopback and private IP addresses was removed from this function. This reintroduces a potential Server-Side Request Forgery (SSRF) vulnerability, as it allows requests to internal services on the server's network. The previous implementation correctly blocked these requests. Please re-add the validation to prevent SSRF.
For reference, the check was similar to this:
import (
"net"
"fmt"
)
// ...
ips, err := net.LookupIP(parsedURL.Hostname())
if err != nil {
return fmt.Errorf("Unable to resolve this URL: %w", err)
}
for _, ip := range ips {
if ip.IsLoopback() || ip.IsPrivate() {
return fmt.Errorf("Access to private IP %s is forbidden", ip.String())
}
}| } | ||
|
|
||
| func UpdateInbox(c *gin.Context) { | ||
| id := c.Param("id") |
There was a problem hiding this comment.
The id from the path parameter is not validated. For consistency with other endpoints (like CreateInbox) and for better security, please validate the format of the id using util.IsValidID.
id := c.Param("id")
if !util.IsValidID(id) {
c.JSON(http.StatusBadRequest, util.APIResponse[any]{Msg: "ID must only contain lowercase letters, numbers, hyphens, and underscores"})
return
}| for _, key := range sortedKeys { | ||
| parts = append(parts, key+"="+values[key]) | ||
| } | ||
| return util.GetPasswordMD5Hash(strings.Join(parts, "\n")) |
There was a problem hiding this comment.
The function GetPasswordMD5Hash is being used here, but the project's documentation in AGENTS.md specifies that this function is intended for password hashing flows only. For general-purpose hashing like generating this GUID, util.GetTextContentHash (which uses FNV-64a) is the recommended function. Using a function against its documented purpose can be misleading. Please use the recommended hashing function for consistency and clarity.
| return util.GetPasswordMD5Hash(strings.Join(parts, "\n")) | |
| return util.GetTextContentHash(strings.Join(parts, "\n")) |
Co-authored-by: Colin <Colin_XKL@outlook.com>
Co-authored-by: Colin <Colin_XKL@outlook.com>
Summary
Walkthrough
craft_admin_pages_unified_walkthrough.mp4
Testing
pnpm run testpnpm run type:checkpnpm run lint(passes with existingFeedItemContent.vuevue/no-v-htmlwarnings)pnpm run buildtask fix(passes with existingFeedItemContent.vuevue/no-v-htmlwarnings)go test ./...task backend-buildtask frontend-buildTo show artifacts inline, enable in settings.