Routable Tabs Configuration

Ordinary tabs keep all their content in the page. Routable tabs give each tab a URL and let the router load it. The page's route gets a tabs array; each tab route has a path relative to the page, the id of the .tab it fills, and a content source such as component, url or async. Notes keeps its URL, /books/3/notes/:

The book route in src/js/routes.js, now with routable tabsJavaScript
const bookRoute = {
  path: '/books/:id(\\d+)/', // digits only, so /books/top/ below can match
  name: 'book',
  options: { props: { source: 'route' } },
  async({ app, to, resolve, reject }) {
    // loads the book with getBook() and resolves BookPage (Sections 2.7.5 and 2.7.6)
  },
  tabs: [
    { path: '/', id: 'tab-details', component: DetailsTab },
    { path: '/notes/', id: 'tab-notes', component: NotesTab },
  ],
};

Each tab is an .f7 component that receives the route's id in props and logs from $onMounted:

Switching routable tabs from the DevTools consoleJavaScript
const router = app.views.main.router;
const pause = () => new Promise((done) => setTimeout(done, 700));
for (const url of ['/books/3/', '/books/3/notes/', '/books/3/']) {
  router.navigate(url);
  await pause();
  console.log(`${url} history=${JSON.stringify(router.history)}`);
}
Output
details tab mounted for book 3
/books/3/ history=["/","/books/3/"]
notes tab mounted for book 3
/books/3/notes/ history=["/","/books/3/notes/"]
details tab mounted for book 3
/books/3/ history=["/","/books/3/"]

The router flattens each tab into a full route (/books/:id/notes/). A target with the same parent route and parameters loads a tab instead of a page and overwrites the last history entry, so Back leaves the book rather than stepping through tabs. unloadTabContent (default true) empties the tab you leave, so Details mounted twice.