Skip to content

Fix resolve plugin view titles on the detail page and view iframe - #10506

Open
lingyun14beta wants to merge 5 commits into
AstrBotDevs:masterfrom
lingyun14beta:fix-resolve-plugin-view-titles-on-the-detail-page-and-view-iframe
Open

lingyun14beta wants to merge 5 commits into
AstrBotDevs:masterfrom
lingyun14beta:fix-resolve-plugin-view-titles-on-the-detail-page-and-view-iframe

Conversation

@lingyun14beta

@lingyun14beta lingyun14beta commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Refs #10497. Follow-up to the sidebar fix in #10502.

#10502 made the sidebar resolve view titles, and the field table it added to docs/zh/dev/star/guides/plugin-pages.md (and the English mirror) states that title is used by the sidebar, the WebUI shell and the plugin detail page.

The plugin detail page and the pageTitle handed to plugin view iframes do not go through that path though. They resolve through dashboard/src/utils/pluginI18n.js, which is still hardcoded to the legacy pages namespace:

return pageName ? `pages.${pageName}` : "";
return page ? `pages.${page}` : "";

So a plugin that follows the docs and writes views.<view>.title shows its localized title in the sidebar and the raw view directory name everywhere else — the sidebar says 设置 while the plugin detail page says settings. A plugin still using the legacy pages.<view>.title namespace resolves correctly on every surface, which makes following the documentation the worse choice.

Modifications / 改动点

dashboard/src/utils/pluginI18n.js

  • Resolve views.<view>.<attr> first and fall back to pages.<view>.<attr>, deriving the view name from whichever prefix the API reported, so the client keeps working against older backends that report pages.<name>.

astrbot/dashboard/services/plugin_page_service.py

  • Emit the canonical views.<name> i18n_key instead of pages.<name>, matching what the docs describe as canonical.
  • Report a plugin that ships both views/ and pages/. View discovery stops at the first directory in PLUGIN_PAGE_ROOT_DIR_NAMES that exists, so every view living only under pages/ is currently discarded without a word. Plugins that still keep their views in pages/ are reported once as well, so authors get a migration hint instead of nothing. Discovery runs again on every plugin list request, so both reports are guarded by _WARNED_VIEW_LAYOUTS and fire at most once per plugin per process.

Tests

  • tests/unit/test_plugin_view_layout.py (new): views/ alone stays silent, a pages/-only plugin is reported exactly once and stays quiet on the second discovery pass, a shadowed pages/ directory is reported as ignored, and a plugin without view directories still raises FileNotFoundError.
  • tests/test_dashboard.py: the two plugin page component assertions now expect the views. prefix.

Docs (docs/zh, docs/en)

  • Add a "从 Pages 迁移到 Views" / "Migrating from Pages to Views" section with an old → new mapping for the view directory, the i18n namespace, the bridge global, and the v1 API routes.
  • Warn that views/ wins when both directories exist and that the log points it out, and that a views: entry in metadata.yaml only feeds plugin metadata instead of affecting discovery.
  • Add matching entries to changelogs/unreleased.md.

This PR intentionally does not touch the sidebar (usePluginSidebarItems.ts, NavItem.vue, sidebarItem.ts) — #10502 already covers that path, including the dual-namespace lookup and the grouped/ungrouped fallback. Nor does it touch the ##视图国际化 heading, which #10502 replaced with ## 视图元数据与国际化.

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

image

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。

  • 📚 I checked the affected WebUI instructions and screenshots in docs/zh and docs/en against the changed navigation, page structure, and labels, and updated them in this PR (or explained why no documentation update is needed). For renamed, moved, or merged entry points, I included an old entry → new entry mapping in the documentation and changelog.
    / 我已对照变化后的 WebUI 入口、页面结构和术语,核对并在本 PR 中更新 docs/zh 和 docs/en 的相关操作说明与截图(或说明无需更新文档的原因)。入口改名、移动或合并时,已在文档和 changelog 中补充 旧入口 → 新入口 对照。

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txt 和 pyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Resolve plugin view titles consistently across the detail page and iframe while preserving compatibility with legacy plugin layouts and translations.

Bug Fixes:

  • Fix plugin detail page titles and iframe page titles to resolve the canonical views.<view>.* translations while retaining compatibility with the legacy pages.<view>.* namespace.

Enhancements:

  • Standardize plugin view metadata on the views i18n namespace and document migration from Pages to Views, including directory discovery behavior and compatibility aliases.

Documentation:

  • Add English and Chinese migration guidance covering view directories, i18n namespaces, bridge globals, API routes, and mixed-directory discovery behavior.

Tests:

  • Update dashboard assertions for canonical view i18n keys and add coverage for legacy, shadowed, and missing plugin view layouts.

AstrBotDevs#10502 made the sidebar resolve view titles, but the plugin detail page and
the pageTitle handed to plugin view iframes go through pluginI18n.js, which
was still hardcoded to the legacy "pages" namespace. A plugin that follows
the docs and writes "views.<view>.title" therefore showed its localized
title in the sidebar and the raw view directory name everywhere else.

- Resolve both namespaces in pluginI18n.js, preferring "views", and derive
  the view name from whichever prefix the API reported so the client keeps
  working against older backends
- Emit the canonical "views.<name>" i18n_key from the plugin page service
- Report a plugin that ships both views/ and pages/: discovery stops at the
  first directory that exists, so every view under pages/ is silently
  ignored. Report pages/-only plugins once as well, so authors get a
  migration hint instead of nothing
- Add an old to new mapping for the view directory, the i18n namespace, the
  bridge global, and the v1 API routes to the plugin view guide (zh/en)
- Warn that "views/" wins when both directories exist and that the log
  points it out, and that a "views:" entry in metadata.yaml only feeds
  plugin metadata instead of affecting view discovery

@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 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="tests/unit/test_plugin_view_layout.py" line_range="71" />
<code_context>
+        assert await service.resolve_plugin_pages_root(plugin) == plugin_root / "pages"
+        second_pass = [record.getMessage() for record in caplog.records]
+
+    assert len(first_pass) == 1
+    assert "pages/" in first_pass[0] and "views/" in first_pass[0]
+    # Discovery runs on every plugin list request, so it must stay quiet after
</code_context>
<issue_to_address>
**Warning tests miss emitted logs**

When the new layout-warning tests run with AstrBot’s configured logger, `caplog` captures records through pytest’s logging handler, but `logger` is AstrBot’s logger with `propagate=False`; the warning never reaches that handler, so `first_pass` is empty and the new assertion fails. The same capture issue breaks the shadowed-directory assertion.

Capture the AstrBot logger with a dedicated test handler or mock the warning call instead of relying on root-level `caplog` capture.
</issue_to_address>

### Comment 2
<location path="astrbot/dashboard/services/plugin_page_service.py" line_range="485-497" />
<code_context>
+        plugin_root: Path,
+        selected_dir: str,
+    ) -> None:
+        """Warn once when a plugin keeps views in a directory that gets ignored.
+
+        Args:
+            plugin: Plugin metadata owning the view directory.
+            plugin_root: Plugin root directory.
+            selected_dir: The view directory name discovery selected.
+
+        Note:
+            View discovery stops at the first directory in
+            ``PLUGIN_PAGE_ROOT_DIR_NAMES`` that exists, so a plugin shipping
+            both ``views/`` and ``pages/`` silently loses every view that only
+            lives under the shadowed directory.
+        """
+        warn_key = f"{plugin.name}:{selected_dir}"
+        if warn_key in _WARNED_VIEW_LAYOUTS:
</code_context>
<issue_to_address>
**Legacy warning docstring is inaccurate**

The docstring says this helper warns when a view directory is ignored, but the `pages/`-only branch also warns even though that directory remains supported and is scanned. Maintainers reading the helper description get the wrong account of when it warns.

Update the docstring to describe both shadowed-directory warnings and migration hints for legacy-only layouts.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: tests/unit/test_plugin_view_layout.py:71


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread tests/unit/test_plugin_view_layout.py Outdated
Comment thread astrbot/dashboard/services/plugin_page_service.py Outdated
astrbot.core.logger sets propagate = False, so pytest's caplog only sees
these warnings through the indirect loguru bridge. Attach the collecting
handler to that logger directly and pin its level for the duration of the
test, so the assertions no longer depend on the root logger or on the order
the rest of the suite leaves the logging configuration in.
The summary only described the shadowed-directory case, but the helper also
reports plugins that keep their views in pages/ only. Those still work and
get a migration hint rather than a warning about lost views; say so.

@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.

Sourcery assessment

Approved.

return pages_root
raise FileNotFoundError("Plugin views root directory does not exist")

async def _warn_legacy_view_layout(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

unnecessary, this can be removed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed in 7dee588

Soulter asked for this to go. It fires for every plugin that still keeps its
views in pages/, which is noise for a layout that is supported and works.

Removes the helper, the once-per-plugin guard, the logger import it needed,
the unit tests that only existed to cover it, and the changelog and docs
lines that mentioned the log output. The Pages to Views migration notes and
the i18n namespace fix stay.

This branch has not been deployed

No deployments
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