Skip to content

Home page / multiple projects - #1333

Draft
microbit-matt-hillsdon wants to merge 6 commits into
mainfrom
home-page
Draft

microbit-matt-hillsdon wants to merge 6 commits into
mainfrom
home-page

Conversation

@microbit-matt-hillsdon

Copy link
Copy Markdown
Collaborator

This is our integration branch.

There's still quite a bit to resolve before this is ready to merge back, along with coordination issues around documentation, videos, PD and similar.

* Let FileSystem switch to a different storage

Groundwork for a project list: opening a project will swap the
FileSystem's backing storage rather than replace files one by one.

switchStorage waits for any in-flight initialisation, makes the new
storage the record, refills the hex file system from it and bumps the
version of every file it holds so an editor showing a same-named file
reloads it. Versions increment rather than reset because the editor
keys on name plus version; a reset to 1 could collide with the old
project's version and leave stale content on screen.

* Persist the current project in an IndexedDB project database

The editor's file system is now mirrored to IndexedDB rather than
session storage, as the first step towards a library of projects.
Behaviour is unchanged for the user: one project per tab, and a new tab
gets a new project.

- projects-db.ts: the database. Two stores, project metadata and files
  keyed by [projectId, name], so listing is cheap and a change writes
  only the files it touched. The database name includes the base path
  because production, beta and review builds share an origin. Opening
  asserts the stores exist so an old build fails clearly against a
  database a newer one created.
- indexeddb-storage.ts: an FSStorage for one project, used as the
  SplitStrategyStorage secondary. Keystroke writes are coalesced per file
  and flushed in one transaction after a short delay and when the page
  is hidden. A failed flush is reported, not thrown, and its changes
  dropped: the in-memory primary still has them and retrying forever
  against a full quota helps nobody.
- current-project.ts: which project a tab opens. The tab's current
  project id lives in session storage. A session-storage file system
  from before this change is migrated into the library on first load,
  so a reload after deploy lands in the user's work; only the file
  system keys are removed, since session settings live there too.
  Without IndexedDB, or with an incompatible database, the editor falls
  back to session storage as before.
- SplitStrategyStorage accepts a promise of its secondary, since opening
  IndexedDB is asynchronous and the Host API is not.

The shared FSStorage tests move to storage-tests.ts so the new storage
runs them too, against fake-indexeddb.

* Share one project library across review builds

Review builds are internal, and a project made on one branch is useful
on the next, so they share a database rather than each having their own
by base path. Production and beta keep separate libraries.

The cost is that a schema change can leave the shared library
incompatible with an older build. On non-public stages that now shows a
"Breaking change to stored data" page offering to clear the database and
reload, as ml-trainer does, instead of quietly falling back to session
storage. Public stages keep the quiet fallback. The outcome of opening
the database is held in a small external store, since storage opens at
module load before React mounts.

* Drop the dirty flag from the projects database and its before-unload prompt

The dirty flag means "changed since the last hex save" and exists only to
warn before work is lost: the before-unload prompt and the replace-project
confirmation are its only readers. Neither applies to a project in the
database, which outlives the tab, so the flag is no longer stored there and
IndexedDBFSStorage always reports not dirty. Session storage keeps it and
FileSystem still tracks it for the tab, so the replace confirmation works for
edits made in this tab until the replace flows go.

openCurrentProjectStorage now reports through storage-status when the
projects database is active, and BeforeUnloadDirtyCheck registers nothing in
that case. The session-storage fallback and iframe mode keep the prompt.

The edits e2e spec now asserts the prompt is absent and that text typed just
before a reload survives via the pagehide flush. The old helper proved
nothing: Playwright accepts an unlistened beforeunload dialog itself, so the
new one listens for it.

* Add e2e coverage for the session-storage fallback and iframe controller mode

The three storage modes now behave differently and only the projects
database path was exercised in the browser.

storage-errors.test.ts uses a new noIndexedDB fixture option, an init script
that hides indexedDB before the app loads, and checks the fallback keeps the
before-unload prompt and survives a reload.

iframe.test.ts embeds the editor with controller=1 and checks the
workspacesync, workspaceloaded, workspacesave and importproject messages and
the prompt. The host page is an HTML string in the spec, served by
intercepting a request for it, so it is never part of the build.
This is a first cut, ported from ml-trainer by extracting shared components
that now live in the ui project.

Lots still remains to agree and implement tracked in docs/home-page.md
that will be resolved before this lands on main.

Detail from squash:

* Take @microbit/ui-carousel for the home and projects pages

Adds the carousel package and its swiper peer, includes its source in the
Panda extraction, and extends dev:link-ui so a local ../ui checkout supplies
it alongside ui and ui-patterns.

* Move the editor to /project

The home and projects pages take the root, as decided in the multi-project
plan. Documentation links to the editor's old /:tab/:slug paths redirect to
the same tab under /project; a #project: link at the root redirects to the
editor with its hash, since microbit.org pages still link there. Anything
else is the shared not-found page.

Iframe controller mode keeps the editor at the root as its only route: the
embedding page owns the URL and there are no other pages. The mode is
provided through context so the URL builders pick the right shape.

The host strips a #project: hash from the current URL rather than the boot
URL, which no longer names the page the editor is on after the redirect.

* Choose the editor's project when its route loads

Projects lists, creates, opens, renames, duplicates and deletes projects
and keeps the file system on the open one. The host now takes the file
system's persistent storage as a promise that Projects resolves when a
project is first opened, so the pages can render without choosing one;
later opens go through FileSystem.switchStorage. The database opens at
boot and reports itself active; the editor route's loader asks Projects
for a project, sharing one choice between concurrent calls.

The tab's project is chosen as before, then the most recent project rather
than a new one, so a straight-to-editor bookmark reopens your work. A new
project is created with the starter program so it can be opened by
switching, which does not run the host's initialisation.

Pending writes are stamped with the edit time rather than the flush time,
so "last modified" orders projects by when they were edited. Flushes report
a change and the IndexedDB storage no longer closes the shared connection.
Tabs tell each other about changes over a BroadcastChannel; a tab with the
changed project open reloads it, or lets go of a deleted one.

The router is created once at module level: a browser router runs its
loaders on creation and StrictMode's double render made two, each creating
a project.

* Add the home and projects pages

Composed from @microbit/ui-patterns' project components and
@microbit/ui-carousel, with app-local layout, banner, resource cards and
what each action does. The home page shows the banner, the user's projects
with a new-project card and a link to see them all, and rows of project
ideas, teacher resources and help. The projects page has search, sort,
multi-select and a toolbar that slides in from the bottom on narrow
screens. Both take their list from the route loader, so there is no empty
flash, and stay current through Projects' change events.

Without the projects database the pages redirect to the editor, which is
then the whole app. A #project: link at the root goes to the editor with
its hash. Opening a project resets the editor's file to main.py.

Theme images resolve per file, as in ml-trainer, so the branded package
only ships what differs; the OSS defaults are neutral placeholders.
Project management events follow ml-trainer's names and are documented.
The carousel's catalog joins the compiled messages.

* Add a home button to the editor's sidebar header

Shown only when the projects database is active and not in iframe mode,
where there is no home page to go to.

* Add e2e coverage for the home and projects pages and the new routes

Page objects for the home and projects pages, with the card and menu
helpers shared between them, and specs for creating, opening, renaming,
duplicating and deleting projects from cards and the toolbar, search and
sort, the legacy documentation redirects, not-found, a #project: link at
the root, and a new tab opening the most recent project.

* Make the sidebar logo the way home instead of a separate button

With the sidebar collapsed there is no room for a home button beside the
logo, so the logo links to the home page when the projects database is
active outside iframe mode. Otherwise it keeps its microbit.org link, as
the fallback and iframe cases have no home page to go to. Short term;
revisit with the header.

E2e gets an App.goHome helper for the link.

* Show the Python logo in brand colour at the top-left of project cards

ProjectIcon mirrors ml-trainer's component of the same name. On the
projects page it drops below the selection checkbox's 60px hover strip so
the two do not crowd the corner; the home row gets a matching inset. The
theme's spacing scale is smaller than Panda's default, so the margins were
measured in the browser rather than taken from ml-trainer.

* Make a #project: link a new project when the projects database is active

PendingMigration holds the link the app booted with and hands it out once.
The Projects session takes it when the editor first chooses a project and
creates one from it, ahead of the tab's current project, so a link no
longer overwrites whatever was open. Without the database the host takes
it as before and writes it into the single implicit project.

The editor route's loader redirects off the hash once the link has become
a project; the session cannot strip it itself because the router commits
the navigation, hash included, after the loader returns.

* Import files as new projects, and add files to the open project

A hex is a whole program, so it always becomes a new project, whether
dropped on the editor, chosen from the Files tab, or imported from the
home page. Other files join the open project from the editor, replacing
any with the same names, and become a new project from the home page,
where a single script becomes main.py and names the project. Ideas from
the documentation open as new projects too. Without the projects database
a hex or idea replaces the single implicit project, with no confirmation.

ProjectImporter (src/project/project-import.tsx) holds this for both
surfaces. The confirm-replace dialog, the choose-main-script dialog and
the replace-or-add flow go, along with Reset project; replacing a program
now means importing from the home page. The Project tab is Files, Open is
Add files and Open leaves the action bar.

FileSystem.filesFromHex reads a hex with a separate file system so the
open project is untouched; the MicroPython hex is fetched once for both.

project_import gains a surface param; project_reset is retired.

* Align the home page projects row with ml-trainer

Use the solid button variant for Import, rename the row to "My projects"
with a "View all" link, and add the info tooltip explaining that projects
are stored in the browser. The tooltip text is copied from ml-trainer.

Tidying the catalogs also removes translations of strings that an earlier
commit dropped from English, which the CI tidy check requires.

* Let resource cards fill their carousel slide

The carousel fixes slides at 260px. Copying ml-trainer's token width
gave 225px under this app's dense preset, leaving slack inside every
slide so the resource rows looked more widely spaced than the projects
row.

* Link the private theme package with a script and align home page content with ml-trainer

Add dev:link-theme, which builds ../python-editor-v3-microbit and symlinks
it into node_modules, so the branded home page is the routine local view.
Rename the help row to "Help" and point the banner's Learn more at the
brand's user guide link, hidden when a deployment has none.

* Show a persistent toast when saving to the projects database fails

A failed flush raises a saveerror event on the Projects session, logged
to Sentry once per session because writes are per keystroke. The toast
and its strings are ml-trainer's so Crowdin dedupes them: a quota error
says browser storage is full, anything else that the save failed. Page
actions that hit the quota show the same toast; their other failures
keep the generic unexpected-error toast, since "failed to save" would
misdescribe a failed delete or rename.

* Show the organisation logo and Python Editor wordmark in the page header, and drop file names from project cards

BrandConfig gains AppLogo and OrgLogo with ml-trainer's LogoProps shape
so the brand packages match. The header shows the organisation logo, a
divider and the wordmark at the prototype's sizes; the OSS build has a
text wordmark and no organisation logo. The header buttons are pinned
to ml-trainer's 48px/24px because this app's dense preset would shrink
them; the menus take a css override for that.

Project cards no longer list file names, which almost always read
main.py. The names are still loaded for the projects page search.

* Reword the home page banner in microbit.org's terms

"Python for the BBC micro:bit" and "Write a program, try it in the
simulator, then send it to your micro:bit." replace the flash and
MicroPython wording, which is ours rather than a student's.

* Make the projects page's back-to-home button a white pill with ml-trainer's arrow

The ghost variant did not stand out on the brand colour. BackArrow is
copied from ml-trainer unchanged rather than promoted to @microbit/ui.

* Use the plain button variant for the page header's settings and help menus

The menus' sidebar variant, made for the editor's black chrome, brought
its white-pill hover to the purple header. The family shows no hover
state on icon buttons in the brand-coloured bar, so the pages pass the
base recipe's plain variant; the editor keeps sidebar.

* Update the home spec for the reworded banner heading

* Scope the editor's drop target to the editor, give the projects page one, and confirm before replacing files

The editor's drop target had moved from around the editor to around the
router when the pages arrived, so a drop on a page took the editor path.
On the projects page in a tab with no project open yet that write waited
silently on the storage promise and landed in whatever project was opened
next; on the home page the nested page target took the drop and the outer
overlay, never seeing a drag-leave, stayed over the editor. The target now
wraps the editor routes only, and the projects page has its own with the
home page's semantics: the files make a new project that opens in the
editor.

Without the projects database a hex or idea asks before replacing an
edited project again, with the strings from main so translations survive;
the before-unload prompt only guards closing the tab, not an in-tab
replace. Adding files that would overwrite existing ones asks first in
every mode. load-error-mixed no longer claims a hex replaces the project.

* Show the beta notice as a band under the page header and drop the sidebar notice's More button

The pages had no notice; they now show ml-trainer's band with its longer
copy and a Feedback button, under the header. The editor keeps its short
sidebar notice. Both are English only, as they show on non-public stages
only. The welcome video is reached from the welcome dialog alone.

* Take the project idea card images from the website's make pages

The three animated gifs give way to the site's stills, so the imports and
the OSS placeholders change extension.

* Use ml-trainer's minimal placeholders for the OSS image fallbacks

A 4x3 grey rectangle for cards and an empty SVG for the banner
background, as ml-trainer has, in place of drawn illustrations.

* WIP notes on the home page/multiple projects work.

* Migrate to the reviewed ui-patterns project component API

- ProjectSearchInput, ProjectSortInput and ProjectSortDirection.
- useProjectActions takes selectedIds as an array; its delete action
  is `delete`. useProjectPageActions passes the array through.
- ProjectCard's onToggleSelected.
- Skip-to-toolbar uses ProjectsToolbar's focus handle instead of a
  wrapper ref and DOM query.
- sortProjects collates in the app locale.

* Add ml-trainer's footer to the home page

Copyright, cookies, privacy and terms; the app store badges are left out.
The copyright holder comes from the brand package, as in ml-trainer.

* Pin the released ui packages and the theme branch build

@microbit/ui 0.5.0, ui-carousel 0.4.0 and ui-patterns 0.7.0 replace the
linked ../ui checkout. The theme version in build.yml is a temporary
build from the theme package's projects-pages branch.

* Fix #project: links on a deployment under a base URL

Two bugs, both only visible where the app isn't served from the root, so
neither shows in local development and both are live on the review build.

The editor's loader redirected to `request.url`'s pathname to drop the
hash, but that carries the basename and `redirect` adds it again, landing
on /v/3/v/3/project. It now strips the basename first.

The home page loader only took a hash starting with #project:, while
microbit.org links put it behind the v2 editor's #import: prefix. Such a
link reached the home page with its program silently dropped.

* Match page URLs against the deployment's base URL in the e2e tests

The assertions took the app to be at the root, so on the review and
staging deployments the home page's URL, which is the base URL without
its trailing slash, matched nothing.

appUrlPattern builds the expected URL from E2E_BASE_URL, as appUrl
already does, and is stricter than the patterns it replaces: /projects
no longer satisfies an assertion for /project.

* Set the e2e cookies on the context, not the app fixture

Tests that drive only the home or projects page never took the app
fixture, so on Foundation builds they met the compliance notice. It is a
modal dialog, which hides the page from the accessibility tree, and the
headings they wait for did not exist.

* Upload the Playwright report CI actually writes

The html reporter writes playwright-report/, so the upload step found
nothing and failing runs left no screenshots or traces behind.

* Serve the base URL without its trailing slash from vite preview

react-router renders the route at "/" as the bare basename, so the app's
own home page link is /projects-pages, not /projects-pages/. The
deployments serve that; vite preview, which the e2e tests run against,
returned its own 404 page, so reloading the home page failed.

* Give the OSS build a logo to click

The sidebar logo links home now that there is a home page, but the
default brand config left squareLogo and horizontalLogo unset. The boxes
that hold them take their height from their content, so an OSS build had
a zero-height link and no way to reach the home page. It also stopped the
e2e tests running against a build without the brand package.

* Size the drop targets to their content so the home footer sits at the bottom

FileDropTarget fixed its height at 100% of its parent, which suited the
editor's full-height pane but sized the home page's target to one viewport
regardless of the content inside it. The rows overflowed the target and the
new footer was laid out at the one-viewport mark, roughly half way down the
page, with the drag overlay only dimming the first screen.

The target now takes its layout from the caller: the editor keeps the full
height, the home and projects pages grow with their content. On tall windows
the footer is pushed to the bottom of the window.
This will make it easier to discuss when it should show.
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.

1 participant