《华尔街日报》在《中国新五年规划为与美国更激烈的科技对抗做准备》(10/24/2025)一文中,检讨了过去西方媒体对习近平的轻视,重新评价了中国于2015年发布的《中国制造2025》,认为该计划为该国设定了要主导电动汽车、航空航太和半导体等尖端技术的目标并成功实现其中许多目标,在催生一批全球赢家的同时,也扰乱了相关产业的全球格局。该文引用德国墨卡托中国研究中心(Mercator
Institute for China Studies)的卡特娅·德林豪森(Katja
Drinhausen)的说法,承认在“习近平上台时,这些规划仍被视为官方套话,未必很有分量,但过去十五年的情况表明,这些规划蕴含着影响力,特别是如果与投资、资源和激励措施相结合的话——这套模式已经取得了成效。”
Messages are piped from stdin
and split into payload packages, which are encrypted and sent as ICMP
Echo requests. The payload size per request is currently set to 32
bytes. The first byte is the length of the message and the rest is the
message itself.
The first request contains a salt and an initialization vector needed to decrypt the payloads.
byte 0
bytes 1-15
bytes 16-31
0x3e
salt
initialization vector
An "end" request is sent in order for the receiver to know
when a message is completed. The end request has the following format:
byte 0
bytes 1-31
0x3e
0xffffffff...
When the end request is received, the full message is printed to the screen.
Everything is designed and engineered following my taste
and vision. This is a personal side project for me to learn about
Mastodon and experiment with new UI/UX ideas.
Features
👪 Multiple accounts
🪟 Compose window pop-out/in
🌗 Light/dark/auto theme
🔔 Grouped notifications
🪺 Nested comments thread
📬 Unsent draft recovery
🎠 Boosts Carousel™️
⚡ Shortcuts™️ with view modes like multi-column or tab bar
#️⃣ Multi-hashtag timeline
Design decisions
Status actions (reply, boost, favourite, bookmark, etc) are hidden by default. They
only appear in individual status page. This is to reduce clutter and
distraction. It may result in lower engagement, but we're not chasing
numbers here.
Boost is represented with the rocket icon. The
green double arrow icon (retweet for Twitter) doesn't look right for the
term "boost". Green rocket looks weird, so I use purple.
Short usernames (@username) are displayed in timelines, instead of the full account username (@username@instance). Despite the guideline
mentioned that "Decentralization must be transparent to the user", I
don't think we should shove it to the face every single time. There are
also some screen-reader-related accessibility concerns with the full username, though this web app is unfortunately not accessible yet.
No autoplay for video/GIF/whatever in timeline. The
timeline is already a huge mess with lots of people, brands, news and
media trying to grab your attention. Let's not make it worse. (Current
exception now would be animated emojis.)
Hash-based URLs. This web app is not meant to be
a full-fledged replacement to Mastodon's existing front-end. There's no
SEO, database, serverless or any long-running servers. I could be wrong
one day.
Subtle UI implementations
User name display
Boosts Carousel
Thread number badge (e.g. Thread 1/X)
Hashtag stuffing collapsing
Filtered posts
Development
Prerequisites: Node.js 20+
npm install - Install dependencies
npm run dev - Start development server and messages:extract (clean + watch) in parallel
npm run build - Build for production
npm run preview - Preview the production build
npm run fetch-instances - Fetch instances list from joinmastodon.org/servers, save it to src/data/instances.json
npm run sourcemap - Run source-map-explorer on the production build
npm run messages:extract - Extract messages from source files and update the locale message catalogs
npm run git:po-filter - Configure git to use po-filter for diffing .po files
Some of these may change in the future. The front-end world is ever-changing.
Internationalization
All translations are available as gettext.po files in the src/locales folder. The default language is English (en). CLDR Plural Rules
are used for pluralization. RTL (right-to-left) languages are also
supported with proper text direction, icon rendering and layout.
On page load, default language is detected via these methods, in order (first match is used):
URL parameter lang e.g. /?lang=zh-Hant
localStorage key lang
Browser's navigator.language
Users can change the language in the settings, which sets the localStorage key lang.
Be attentive to placeholders for variables. Many strings have placesholders e.g. {account} (variable), <0>{name}</0> (tag with variable) and # (number placeholder).
IDs for strings are auto-generated instead of explicitly defined. Some of the benefits are avoiding the "naming things" problem and avoiding duplicates.
Explicit IDs might be introduced in the future when requirements and priorities change. The library (Lingui) allows both.
Please report issues if certain strings are translated differently based on context, culture or region.
There are no strings for push notifications. The language is set on the instance server.
Native HTML date pickers, e.g. <input type="month"> will always follow the system's locale and not the user's set locale.
"ALT" in ALT badge is not translated. It serves as a a recognizable standard across languages.
Custom emoji names are not localized, therefore searches don't work for non-English languages.
Unicode Right-to-left mark (RLM) (U+200F, ‏) may need to be used for mixed RTL/LTR text, especially for <title> element (document.title).
On development, there's an additional pseudo-LOCALE locale, used for pseudolocalization. It's for testing and won't show up on production.
When building for production, English (en) catalog
messages are not bundled separatedly. Other locales are bundled as
separate files and loaded on demand. This ensures that en is always available as fallback.
Volunteer translations
Translations are managed on Crowdin. You can help by volunteering translations.
This is a pure static web app. You can host it anywhere you want.
Two ways (choose one):
Easy way
Go to Releases and download the latest phanpy-dist.zip or phanpy-dist.tar.gz. It's pre-built so don't need to run any install/build commands. Extract it. Serve the folder of extracted files.
Important
Text translations connect to an external service (translang.phanpy.social).
Download or git clone this repository. Use production branch for stable releases, main for latest. Build it by running npm run build (after npm install). Serve the dist folder.
Important
Text translations connect to an external service (translang.phanpy.social). This can be configured with environment variables if you want to self-host your own instance.
Customization can be done by passing environment variables to the build command. Examples:
PHANPY_CLIENT_NAME="Phanpy Dev" \
PHANPY_WEBSITE="https://dev.phanpy.social" \
npm run build
PHANPY_DEFAULT_INSTANCE=hachyderm.io \
PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL=https://hachyderm.io/auth/sign_up \
PHANPY_PRIVACY_POLICY_URL=https://hachyderm.io/privacy-policy \
npm run build
When logging in, the user will be redirected instantly to the
instance's authentication page instead of having to manually type the
instance URL and submit
PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL (optional, no defaults):
URL of the instance registration page
E.g. https://mastodon.social/auth/sign_up
PHANPY_PRIVACY_POLICY_URL (optional, default to official instance's privacy policy):
URL of the privacy policy page
May specify the instance's own privacy policy
PHANPY_DEFAULT_LANG (optional):
Default language is English (en) if not specified.
Fallback language after multiple detection methods (lang query parameter, lang key in localStorage and navigator.language)
This is applied with the <meta> tag on the client-side.
The policy can also be set with Referrer-Policy header configured on the server-side (not this variable).
Note that since Phanpy uses hash-based URLs, the referrer does not include the hash part.
PHANPY_LINGVA_INSTANCES (DEPRECATED, optional, space-separated list, default: lingva.phanpy.social [...hard-coded list of fallback instances]):
Specify a space-separated list of instances. First will be used as
default before falling back to the subsequent instances. If there's only
1 instance, means no fallback.
Specify a space-separated list of instances. First will be used as
default before falling back to the subsequent instances. If there's only
1 instance, means no fallback.
May specify a self-hosted Translating instance, powered by translang-api.
List of instances hard-coded in /.env
PHANPY_IMG_ALT_API_URL (optional, no defaults):
API endpoint for self-hosted instance of img-alt-api.
If provided, a setting will appear for users to enable the image description generator in the composer. Disabled by default.
I am one of the earliest users of Twitter. Twitter was launched on 15 July 2006. I joined on December 2006 and my first tweet was posted on 18 December 2006.