diff --git a/.browserlist b/.browserlist
deleted file mode 100644
index b547231..0000000
--- a/.browserlist
+++ /dev/null
@@ -1 +0,0 @@
->0.3% and not dead, firefox>=98, safari>=15.4, chrome>=84
\ No newline at end of file
diff --git a/README.md b/README.md
index 0bb7288..a4c432a 100644
--- a/README.md
+++ b/README.md
@@ -8,17 +8,17 @@ Tutu is a comfortable experience for tooting. Designed to work on any device - d
The code is built against those targets and Tutu must run on those platforms:
-| Firefox | Safari | iOS | Chrome | Edge |
-| ------- | ------ | ----- | ------ | ---- |
-| 98 | 15.4 | 15.4 | 84 | 87 |
+| Firefox | Safari | iOS | Chrome & Edge |
+| ------- | ------ | ----- | ------------- |
+| 115 | 15.6 | 15.6 | 108 |
Tutu trys to push the Web technology to its limit. Some features might not be available on the platform does not meet the requirement.
-## The "Next" Branch
+## The "Nightly" Branch
-The "next" branch of the app is built on every commit pushed into "master". You can tatse latest change but risks your data.
+Tutu built on the latest code, called the nightly version. You can tatse latest change but risks your data.
-[Launch Tutu (Next)](https://master.tututheapp.pages.dev)
+[Launch Tutu (Nightly)](https://master.tututheapp.pages.dev)
## Build & Depoly
diff --git a/bun.lockb b/bun.lockb
index bec25b1..cd4221a 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/docs/devnotes.md b/docs/devnotes.md
index 577c968..6e10652 100644
--- a/docs/devnotes.md
+++ b/docs/devnotes.md
@@ -84,3 +84,64 @@ But, sometimes you need a redesigned (sometimes better) tool for the generic usa
- *What* this new tool does?
- *How* this tool works?
- Clean up code regularly. Don't keep the unused code forever.
+
+## Managing CSS
+
+Two techniques are still:
+
+- Styled compoenent (solid-styled)
+- Native CSS with CSS layering
+
+The second is recommended for massive use. A stylesheet for a component can be placed alongside
+the component's file. The stylesheet must use the same name as the component's file name, but replace the extension with
+`.css`. Say there is a component file "PreviewCard.tsx", the corresponding stylesheet is "PreviewCard.css". They are imported
+by the component file, so the side effect will be applied by the bundler.
+
+The speicifc component uses a root class to scope the rulesets' scope. This convention allows the component's style can be influenced
+by the other stylesheets. It works because Tutu is an end-user application, we gain the control of all stylesheets in the app (kind of).
+Keep in mind that the native stylesheets will be applied globally at any time, you must carefully craft the stylesheet to avoid leaking
+of style.
+
+Three additional CSS layers are declared as:
+
+- compat: Compatibility rules, like normalize.css
+- theme: The theme rules
+- material: The internal material styles
+
+When working on the material package, if the style is intended to work with the user styles,
+it must be declared under the material layer. Otherwise the unlayer, which has the
+highest priority in the author's, can be used.
+
+Styled component is still existing. Though styled component, using attributes for scoping,
+may not be as performant as the techniques with CSS class names;
+it's still provided in the code infrastructure for its ease.
+
+The following is an example of the recommended usage of solid-styled:
+
+```tsx
+// An example of using solid-styled
+import { css } from "solid-styled";
+import { createSignal } from "solid-js";
+
+const Component = () => {
+ const [width, setWidth] = createSignal(100);
+
+ css`
+ .root {
+ width: ${width()}%;
+ }
+ `
+ return
+};
+```
+
+When developing new component, you can use styled component at first, and migrate
+to native css slowly.
+
+Before v2.0.0, there are CSS modules in use, but they are removed:
+
+- Duplicated loads
+- Unaware of order (failed composing)
+- Not-ready for hot reload
+
+In short, CSS module does not works well if the stylesheet will be accessed from more than one component.
diff --git a/docs/versioning.md b/docs/versioning.md
new file mode 100644
index 0000000..4d50356
--- /dev/null
+++ b/docs/versioning.md
@@ -0,0 +1,32 @@
+# Versioning & Development Cycle
+
+The versioning policy follows the [Semantic Versioning](https://semver.org/).
+Since Tutu is an app for the end user, we redefine the some words in the policy:
+
+- API changes: the app is no longer available on certain platforms.
+
+## Development Cycle
+
+Dependency Freeze -> Development -> Release
+
+### Dependency Freeze
+
+This step is for:
+
+- Update dependencies
+- Prepare the new version (like, bump the version number).
+
+New dependencies should not be added in this step.
+
+### Development
+
+In this step, dependencies can only be updated if it's required to fix bugs.
+
+New dependencies should be added as their use, in this step.
+
+### Release
+
+The version is released to production in this step.
+
+Before the next development step, new versions can still be released to
+fix bugs.
diff --git a/package.json b/package.json
index 6222ecd..69b71d9 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "tutu",
- "version": "1.1.0",
+ "version": "2.0.0",
"description": "",
"private": true,
"type": "module",
@@ -10,30 +10,40 @@
"preview": "vite preview",
"dist": "vite build",
"count-source-lines": "exec scripts/src-lc.sh",
- "typecheck": "tsc --noEmit --skipLibCheck"
+ "typecheck": "tsc --noEmit --skipLibCheck",
+ "wdio": "wdio run ./wdio.conf.ts"
},
"keywords": [],
"author": "Rubicon",
"license": "Apache-2.0",
"devDependencies": {
- "@solid-devtools/overlay": "^0.30.1",
+ "@solid-devtools/overlay": "^0.33.0",
"@suid/vite-plugin": "^0.3.1",
+ "@testing-library/webdriverio": "^3.2.1",
"@types/hammerjs": "^2.0.46",
"@types/masonry-layout": "^4.2.8",
"@vite-pwa/assets-generator": "^0.2.6",
+ "@wdio/cli": "^9.5.1",
+ "@wdio/lighthouse-service": "^9.5.1",
+ "@wdio/local-runner": "^9.5.1",
+ "@wdio/mocha-framework": "^9.5.0",
+ "@wdio/spec-reporter": "^9.5.0",
"postcss": "^8.4.49",
- "prettier": "^3.3.3",
- "typescript": "^5.6.3",
- "vite": "^5.4.11",
+ "prettier": "^3.4.2",
+ "tsx": "^4.19.2",
+ "typescript": "^5.7.2",
+ "vite": "^6.0.7",
"vite-plugin-package-version": "^1.1.0",
- "vite-plugin-pwa": "^0.20.5",
- "vite-plugin-solid": "^2.10.2",
+ "vite-plugin-pwa": "^0.21.1",
+ "vite-plugin-solid": "^2.11.0",
"vite-plugin-solid-styled": "^0.11.1",
+ "wdio-vite-service": "^2.0.0",
+ "wdio-wait-for": "^3.0.11",
"workbox-build": "^7.3.0",
- "wrangler": "^3.86.1"
+ "wrangler": "^3.99.0"
},
"dependencies": {
- "@formatjs/intl-localematcher": "^0.5.7",
+ "@formatjs/intl-localematcher": "^0.5.10",
"@nanostores/persistent": "^0.10.2",
"@nanostores/solid": "^0.5.0",
"@solid-primitives/event-listener": "^2.3.3",
@@ -42,8 +52,8 @@
"@solid-primitives/map": "^0.4.13",
"@solid-primitives/page-visibility": "^2.0.17",
"@solid-primitives/resize-observer": "^2.0.26",
+ "@solidjs/router": "^0.15.2",
"@solid-primitives/rootless": "^1.4.5",
- "@solidjs/router": "^0.15.1",
"@suid/icons-material": "^0.8.1",
"@suid/material": "^0.18.0",
"blurhash": "^2.0.5",
@@ -56,9 +66,10 @@
"masto": "^6.10.1",
"nanostores": "^0.11.3",
"normalize.css": "^8.0.1",
- "solid-devtools": "^0.30.1",
+ "solid-devtools": "^0.33.0",
"solid-js": "^1.9.3",
"solid-styled": "^0.11.1",
+ "solid-transition-group": "^0.2.3",
"stacktrace-js": "^2.0.2",
"workbox-core": "^7.3.0",
"workbox-precaching": "^7.3.0"
diff --git a/src/App.css b/src/App.css
index 70ee2fb..d2643a9 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,41 +1,46 @@
-@import "normalize.css/normalize.css";
-@import "./material/theme.css";
+@layer compat, theme, material;
-:root {
- --safe-area-inset-top: env(safe-area-inset-top);
- --safe-area-inset-left: env(safe-area-inset-left);
- --safe-area-inset-bottom: env(safe-area-inset-bottom);
- --safe-area-inset-right: env(safe-area-inset-right);
- background-color: var(--tutu-color-surface, transparent);
-}
+@import "normalize.css/normalize.css" layer(compat);
+@import "./material/theme.css" layer(theme);
+@import "./material/material.css" layer(material);
-/*
-Fix the bottom gap on iOS standalone.
-https://stackoverflow.com/questions/66005655/pwa-ios-child-of-body-not-taking-100-height-gap-on-bottom
-*/
-@media screen and (display-mode: standalone) {
- body {
- width: 100%;
- height: 100vh;
+@layer compat {
+ :root {
+ --safe-area-inset-top: env(safe-area-inset-top);
+ --safe-area-inset-left: env(safe-area-inset-left);
+ --safe-area-inset-bottom: env(safe-area-inset-bottom);
+ --safe-area-inset-right: env(safe-area-inset-right);
+ background-color: var(--tutu-color-surface, transparent);
}
- #root {
- position: fixed;
- top: 0;
- left: 0;
- height: 100vh;
- width: 100vw;
+ /*
+ Fix the bottom gap on iOS standalone.
+ https://stackoverflow.com/questions/66005655/pwa-ios-child-of-body-not-taking-100-height-gap-on-bottom
+ */
+ @media screen and (display-mode: standalone) {
+ body {
+ width: 100%;
+ height: 100vh;
+ }
+
+ #root {
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100vh;
+ width: 100vw;
+ }
+ }
+
+ h1 {
+ margin: 0;
+ }
+
+ * {
+ user-select: none;
}
}
.custom-emoji {
width: 1em;
-}
-
-h1 {
- margin: 0;
-}
-
-* {
- user-select: none;
-}
+}
\ No newline at end of file
diff --git a/src/accounts/MastodonOAuth2Callback.css b/src/accounts/MastodonOAuth2Callback.css
new file mode 100644
index 0000000..c150d03
--- /dev/null
+++ b/src/accounts/MastodonOAuth2Callback.css
@@ -0,0 +1,22 @@
+.MastodonOAuth2Callback {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ width: 448px;
+
+ @media (max-width: 600px) {
+ & {
+ position: static;
+ height: 100%;
+ width: 100%;
+ left: 0;
+ right: 0;
+ transform: none;
+ display: grid;
+ grid-template-rows: 1fr auto;
+ height: 100vh;
+ overflow: auto;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/accounts/MastodonOAuth2Callback.tsx b/src/accounts/MastodonOAuth2Callback.tsx
index 398d79d..d530095 100644
--- a/src/accounts/MastodonOAuth2Callback.tsx
+++ b/src/accounts/MastodonOAuth2Callback.tsx
@@ -8,13 +8,13 @@ import {
} from "solid-js";
import { acceptAccountViaAuthCode } from "./stores";
import { $settings } from "../settings/stores";
-import { useDocumentTitle } from "../utils";
-import cards from "~material/cards.module.css";
+import "~material/cards.css";
import { LinearProgress } from "@suid/material";
import Img from "~material/Img";
import { createRestAPIClient } from "masto";
import { Title } from "~material/typography";
import { useNavigator } from "~platform/StackedRouter";
+import DocumentTitle from "~platform/DocumentTitle";
type OAuth2CallbackParams = {
code?: string;
@@ -27,13 +27,12 @@ const MastodonOAuth2Callback: Component = () => {
const titleId = createUniqueId();
const [params] = useSearchParams();
const { push: navigate } = useNavigator();
- const setDocumentTitle = useDocumentTitle("Back from Mastodon...");
const [siteImg, setSiteImg] = createSignal<{
src: string;
srcset?: string;
blurhash: string;
}>();
- const [siteTitle, setSiteTitle] = createSignal("the Mastodon server");
+ const [siteTitle, setSiteTitle] = createSignal("Mastodon");
onMount(async () => {
const onGoingOAuth2Process = $settings.get().onGoingOAuth2Process;
@@ -42,7 +41,6 @@ const MastodonOAuth2Callback: Component = () => {
url: onGoingOAuth2Process,
});
const ins = await client.v2.instance.fetch();
- setDocumentTitle(`Back from ${ins.title}...`);
setSiteTitle(ins.title);
const srcset = [];
@@ -93,42 +91,45 @@ const MastodonOAuth2Callback: Component = () => {
});
});
return (
-