# 导航与组合

将 shell 的 URL 和导航意图连接到应用的挂载、卸载与状态。

<span id="el-shell-es-dueño-de-la-navegación"></span>

<span class="section-anchor" id="section-1"></span>

## shell 负责导航

shell 决定每条路由对应哪个应用，并通过挂载、卸载、事件和状态组合应用。Astro 可以解析页面；React/Vue router 可以管理应用内部路由。WU core 不包含 `wu.router` 或 `wu.navigate()`：将 shell 使用的导航机制连接到生命周期即可。

`wu.json` 中的 `metadata.routes` 是描述性信息。解析器验证字符串、重复项和限制，但不会安装 History API handlers。Framework 4 拒绝非空的 `wu.routes`，不应将其用作路由方案。

<span id="dos-niveles-de-rutas"></span>

<span class="section-anchor" id="section-2"></span>

## 两个路由层级

| 层级 | 示例 | 负责方 |
| --- | --- | --- |
| 门户页面 | `/docs/framework/state` | Astro 文件路由器 |
| shell 动态区域 | `/workspace/catalog` | shell 控制器/路由器 |
| 应用内部视图 | catalog 内的 `/products/:id` | 该应用的路由器 |

下载 base URL（如 `/apps/catalog`）属于技术资源，不必与导航路由相同。应分别定义 bundle base、内部 router base 和用户看到的路径。

<span id="conectar-una-intención-de-navegación"></span>

<span class="section-anchor" id="section-3"></span>

## 连接导航意图

此示例实现 shell 的一种约定；`shell:navigate` 不会激活内置路由器：

```js
const routeTable = new Map([
  ['/workspace/catalog', 'catalog'],
  ['/workspace/reports', 'reports'],
]);

let activeApp = null;
let transition = Promise.resolve();

function showRoute(path) {
  transition = transition.catch(() => {}).then(async () => {
    const nextApp = routeTable.get(path);
    if (!nextApp || nextApp === activeApp) return;
    if (activeApp) await wu.unmount(activeApp, { force: true });
    activeApp = null;
    await wu.mount(nextApp, '#workspace-slot');
    activeApp = nextApp;
  });
  return transition;
}

const stop = wu.on('shell:navigate', ({ data }) => {
  if (!routeTable.has(data?.path)) return;
  history.pushState(null, '', data.path);
  showRoute(data.path).catch(console.error);
});

const onPopState = () => showRoute(location.pathname).catch(console.error);
window.addEventListener('popstate', onPopState);
await showRoute(location.pathname);

// Al retirar el controlador del shell:
// stop();
// window.removeEventListener('popstate', onPopState);
```

映射表限制可接受的路由和应用。用户在加载结束前多次导航时，队列保留顺序。core 也会协调容器所有权；shell 保持清晰状态，可以避免旧 Promise 更新新路由的导航控件。

<span id="elementos-dentro-de-shadow-dom"></span>

<span class="section-anchor" id="section-4"></span>

## Shadow DOM 内的元素

`wu.mount(name, selector)` 使用 `document.querySelector` 解析选择器。该选择器不会穿越 ShadowRoots，当前签名接受字符串，不接受 HTMLElement。要让 shell 找到 slot，请将其放在 light DOM 中，或通过组合方式暴露可由选择器访问的节点。

生命周期内应使用收到的 `container` 操作局部根节点，不要再通过 ID 全局查找。仓库中的 Vue adapter 展示了为什么稳定根节点应与 loading 指示器并列，而不应使用由 shell 管理的指示器容器。

<span id="keep-alive-y-cambio-de-ruta"></span>

<span class="section-anchor" id="section-5"></span>

## Keep-alive 与路由切换

`hide` 保留状态和监听器，需要保留应用容器。多个应用切换使用同一个 slot 时，必须明确选择：卸载原所有者，或为每个需要保持活动的应用保留独立容器。keep-alive 与模块缓存管理的是不同资源。

如果 Astro 通过客户端导航替换页面，bootstrap 必须配合该生命周期：释放上一页的监听器和 mounts，并连接新 slots。脚本导入去重不代表未来出现的所有节点都会自动挂载。

<span id="roles-de-ui"></span>

<span class="section-anchor" id="section-6"></span>

## UI 角色

`wu.setPrincipal({ id: 'u1', roles: ['catalog-reader'] })` 为 `wu.can('catalog')` 提供身份信息。挂载会检查 manifest 要求的角色，可能以 `WU_ACCESS_DENIED` 拒绝。这有助于保持导航与视图一致；数据 API 仍应在服务器上授权每个请求。

<span id="referencia-de-implementación"></span>

<span class="section-anchor" id="section-7"></span>

## 实现参考

描述性路由验证：`src/core/wu-manifest.js`；访问控制：`src/core/wu-core.js` 和 `src/core/wu-core.js`；选择器：`src/core/wu-core.js`；容器转换：`src/core/wu-core.js`；MCP 使用的约定：`src/core/wu-mcp-bridge.js`。测试：`tests/unit/shared-container-navigation.test.js`、`tests/unit/sandbox-container-ownership.test.js`、`tests/unit/rbac.test.js`。