今日已更新 64 条资讯 | 累计 41741 条内容
关于我们

标签:#webdev

找到 2663 篇相关文章

AI 资讯

How We Handle Client-Side CSV Merging Without Server Processing

When merging CSVs in the browser, handling mismatched columns and quoted cells changes everything. Here's how filetools does it. Last week we shipped CSV merge/split/transpose tools for filetools, and the most interesting challenge wasn't CSV parsing - it was handling real-world data without a server. Here's how we handle the hard cases. The Problem: CSV files in the wild are messy. Columns don't always match. A cell value contains a comma and that comma is quoted. Headers are sometimes case-sensitive, sometimes not. When you build on a server, you can run a fast library and stream the result. In the browser, you have to make your merge operation deterministic from first load. Our approach: Column matching: Users specify which columns to merge on (e.g., "id" or "email"). We do a case-insensitive first pass, then check for exact matches. If no match exists, we warn the user and ask them to pick from the detected headers. This upfront clarity saves merge errors later. Quoted cell handling: We follow RFC 4180 strictly - a quote inside a quoted field is escaped as a double quote. Most CSV parsers get this wrong when they're quick. We use the csv-parse library (MIT) vendored into the site, same way we do with PDF and ZIP libraries. Column order: The merge operation respects column order from the first file, then appends any new columns from subsequent files. This is deterministic and reproducible. Why this matters for a browser tool: Server-based CSV tools hide their assumptions - you upload, they merge, you download. If a merge fails, you get an error message and no insight into why. Client-side, the user can see the detected headers, approve or correct them, and re-try immediately. That transparency matters when you're dealing with data that represents real records or transactions. What shipped this week: We added merge, split (by row count or column value), transpose, and comparison tools. The same deterministic, transparent approach applies to each one. Next question

2026-08-22 原文 →
开发者

Building a Full Enterprise-Ready React + Spring Boot Auth Flow: An End-to-End Guide

Introduction Authentication is one of those things that looks simple in a tutorial and becomes surprisingly complex in production. Between token storage, CSRF protection, refresh flows, and protected routing, there are many places to get it wrong—and getting it wrong has real security consequences. In two earlier posts, I covered pieces of this puzzle: Enabling CSRF in a JWT-Based React + Spring Boot Application and Storing Personal Information in React: sessionStorage vs Context API . This post ties those threads together into a complete, end-to-end authentication flow you can adapt for enterprise applications. We'll walk through the full journey: login → token issuance → secure storage → protected routes → token refresh → logout. Architecture Overview Before the code, here's the high-level flow: ┌──────────────┐ ┌──────────────────┐ │ React │ │ Spring Boot │ │ Frontend │ │ Backend │ └──────┬───────┘ └────────┬─────────┘ │ 1. POST /login │ │─────────────────────────>│ │ │ validate credentials │ 2. JWT (httpOnly cookie)│ issue access + refresh │<─────────────────────────│ │ │ │ 3. GET /protected │ │ (+ CSRF token) │ │─────────────────────────>│ validate JWT + CSRF │ 4. Protected data │ │<─────────────────────────│ │ │ │ 5. POST /refresh │ │─────────────────────────>│ rotate tokens │ │ │ 6. POST /logout │ │─────────────────────────>│ invalidate session Key Design Decisions Decision Choice Rationale Token storage httpOnly cookies Not accessible to JavaScript → mitigates XSS token theft CSRF protection Double-submit / token pattern Required when using cookies Token type Short-lived access + refresh Limits exposure window State management Context API for auth status Centralized, lightweight Why httpOnly cookies over localStorage? As I discussed in the storage blog, localStorage is readable by any script on the page—making it vulnerable to XSS. httpOnly cookies trade that risk for the need to handle CSRF, which we address below. Step 1: Backend — Login and Token Issuance

2026-08-22 原文 →
AI 资讯

Four places ffmpeg.wasm fails silently in a Next.js app (and the fixes)

I shipped four browser-only video tools with ffmpeg.wasm: trim, compress, video-to-GIF and MP3 extraction. Files never leave the browser, nothing to install. Trim · Compress · GIF · MP3 (Korean UI, but the buttons are obvious) Getting there, I hit four walls. Every one of them surfaced as a single "conversion failed" line in the UI and nothing in the console . Writing them down for the next person. Stack: Next.js App Router + webpack, @ffmpeg/ffmpeg 0.12, self-hosted core. 1. webpack hijacks the dynamic import inside the worker @ffmpeg/ffmpeg spawns its worker like this: new Worker ( new URL ( " ./worker.js " , import . meta . url ), { type : " module " }); webpack recognises the pattern and bundles the worker. Fine. But it also rewrites the import(coreURL) inside that worker to go through its own module loader. The core URL arrives at runtime as a blob: URL, which webpack's loader has never heard of, so it dies with Cannot find module 'blob:...' . The error is thrown inside the worker, so the main-thread console stays empty. Fix: keep the worker out of the bundle. Copy node_modules/@ffmpeg/ffmpeg/dist/esm/worker.js to public/ffmpeg/<version>/lib/ and pass it via classWorkerURL in load() . Now the untouched worker runs. 2. classWorkerURL needs the origin Passing a path like /ffmpeg/0.12.x/lib/worker.js is not enough. The library resolves it with new URL(classWorkerURL, import.meta.url) , and inside the bundle import.meta.url is a build-time file:///C:/... path. So it goes looking for file:///C:/ffmpeg/... and fails. const BASE = `/ffmpeg/ ${ FFMPEG_VERSION } ` ; await ffmpeg . load ({ coreURL : ` ${ location . origin }${ BASE } /core/ffmpeg-core.js` , wasmURL : ` ${ location . origin }${ BASE } /core/ffmpeg-core.wasm` , classWorkerURL : ` ${ location . origin }${ BASE } /lib/worker.js` , }); Prefix location.origin and it works. 3. You cannot build a GIF palette with -vf For decent GIF quality you run palettegen first and paletteuse second. Doing it in one pass needs

2026-08-22 原文 →
AI 资讯

My first website said "Don't commit without context." I never committed it at all.

The renewal notice came and I decided to let it go. threadkeeper.io was my first idea and my first website. I bought the domain in August 2025, about six weeks after a community college AI summer camp where I was writing files with names like ccc-ai-pdf-project and describing them in my own README as a beginner Python project. Then I shipped a domain, a blog, a CLI, and a manifesto. Before I let it lapse I went back to look at it one more time. Sentimental. Five minutes, tops. Then I tried to figure out where the source code lived, and realized it did not live anywhere. The site was on Spaceship. I had built it there, in the browser, and never put it in version control. Not once. There was no repo to clone, no local folder, no backup. The only copy of my first website that existed in the world was the one running on a server I had four days left on. The tagline on that site, in cyan, at the top of the page, was "Don't commit without context." I never committed it at all. I did not have the source code to my own website So the first job was not nostalgia. It was extraction. I pulled all eight pages and every asset off the live server before it went dark: the landing page, the blog, three posts, the Dr. Kahlo page, and the Ariadne Clew recap app I built for an AWS hackathon. Nineteen files. sitemap.xml claimed there were four pages, which tells you how much I trusted my own sitemap in 2025. The rest I found by following links. That archive is now public, with a SHA-256 for every original file so anyone can verify nothing drifted in the rescue: earlgreyhot1701d.github.io/threadkeeper-archive It is committed now. A year late. I named a file dom_js.js and did not blink Here is the first thing I found once I could actually read my own code. The Ariadne Clew app had seven JavaScript modules. Two of them were named with snake case and a suffix: api_js.js , dom_js.js , main_js.js . Four were camelCase with no suffix: utils.js , theme.js , exportMarkdown.js , dragDrop.js . Tw

2026-08-22 原文 →
AI 资讯

A rule-based Korean dialect converter with no LLM: eight guards that keep standard Korean intact

I built a Korean dialect converter that runs entirely in the browser and uses no LLM. Type a standard Korean sentence and it rewrites it the way people speak in Gyeongsang, Jeolla, Chungcheong, Gangwon or Jeju. Paste a dialect sentence and it converts back to standard Korean. Nothing leaves the browser. Try it: https://toolnjoy.com/dialect-converter (Korean UI) The hard part was not producing dialect. It was not breaking standard Korean . Every time I widened a rule, a perfectly normal sentence got mangled somewhere. Here is what I ended up with. Most of it applies to any rule-based text rewriter (honorific converters, profanity filters, spelling normalizers). Shape of the thing Four modules: file job core sentence splitting, sentence-type detection, ending replacement endings per-region ending tables (declarative / interrogative / propositive / imperative × politeness) words per-region vocabulary reverse dialect → standard Forward conversion goes "decide the sentence type, then pick the region's ending". Reverse goes the other way: "look at the ending, infer the sentence type". You cannot just invert the forward table, so reverse has its own table. Each reverse rule carries a type , which removes the detection step entirely. In reverse mode the user does not pick a region. I run all five and keep the one with the highest score (4 points per matching ending + syllable count of matched words). People who paste dialect usually do not know which region it is from. That is why they came. Eight guards, each from a real failure 1. Never reverse a form that appears on the standard side of any table. The Gyeongsang table had 함께 → 같이 (together). Reverse flipped it and rewrote "같이 보게" into "함께 보게", except 같이 is perfectly standard. Fix: anything that ever appears in a standard column anywhere is excluded from reverse candidates. 2. Question endings only count when there is a question mark or a wh-word. Gyeongsang -나 / -노 mark questions. But 하나 ("one") also ends in -나 . Without

2026-08-22 原文 →
AI 资讯

The duration your video API accepts is not the duration it renders

A sequence I cut to a music bed was three frames out at the first transition, nine at the second, and by the sixth segment nothing lined up with anything. I had asked every generation for ten seconds. Every generation had returned a file that was not ten seconds. Nothing in the API said so. The request took duration: 10 , returned 200 , and produced an MP4 whose container duration was 8.708 . No warning field, no note in the response body, and — the part that actually cost me the afternoon — no mention of it on the docs page I had read three times. This is a general property of latent video models rather than a bug in one provider, and once you know the shape of it you can handle it in about twenty lines. Here is the shape. Seconds are the wrong unit A video diffusion model does not work on frames. It works on a compressed latent tensor, and the compression is temporal as well as spatial: a causal 3D autoencoder folds a run of input frames into a single latent frame. Because the encoder is causal, the first frame is kept whole and everything after it is compressed in groups. With a temporal stride of s , a clip of F frames becomes latent_frames = ( F - 1 ) / s + 1 which only divides evenly when F ≡ 1 (mod s) . Frame counts that miss that condition get padded or truncated, so implementations pick the nearest legal count and render that instead. Stack a second constraint on top — many of these models generate in fixed blocks of latent frames rather than one at a time — and the set of renderable lengths collapses into a short arithmetic progression: F = head + block · n n ∈ ℕ Every legal duration is one of those F values divided by the frame rate. Nothing between them is reachable. duration: 10 is not a request. It is a hint that gets snapped to a grid you were never shown. What the snapping does to users Three separate problems, and only the first is obvious. The output is not the length you promised. Your UI said 10s, the file is 8.708s, so your UI lied. Not by much,

2026-08-21 原文 →
AI 资讯

Why I Built a No-Signup QR & URL Utility Platform (And How to Use the API)

We've all been there: a client needs a quick QR code for a print campaign, or a short link for social media. You search Google, click the first result, and realize you need to create an account, verify your email, and potentially pay after 14 days. To solve this friction, we built klick.tools . It's a collection of simple web tools that just work - no signup, no expiration dates, and full GDPR compliance. What's inside? QR code generator: 7 content types (URL, text, WiFi, vCard, email, phone, geo), custom colors and module shapes, automatic WCAG contrast check, and clean export as PNG, SVG or PDF. Rendering happens in the browser, so the payload never leaves the device. URL shortener: shorten a link in seconds, see click stats, and change the target later without reprinting anything. The developer API We didn't want to build just another consumer site, so everything is backed by a REST API. Base URL: https://klick.tools/api/v1 . Responses are always JSON - lists as { data, count } , writes as { message, data } , errors as { error } with a stable machine-readable code . Creating a short link is a single POST, and it works without an account at all (rate-limited per IP): curl -X POST https://klick.tools/api/v1/links \ -H "Content-Type: application/json" \ -d '{"targetUrl": "https://example.com/a-very-long-campaign-url"}' With an API key ( kt_live_... , generated in your account) the link is bound to you, so you get click counts, editing and higher quotas. Pass it as a Bearer token or via x-api-key : const res = await fetch ( " https://klick.tools/api/v1/links " , { method : " POST " , headers : { " Content-Type " : " application/json " , Authorization : `Bearer ${ process . env . KLICK_TOOLS_API_KEY } ` , }, body : JSON . stringify ({ targetUrl : " https://example.com/landing " , title : " Summer campaign " , }), }); const { data } = await res . json (); console . log ( data . shortUrl , data . clickCount ); The same pattern covers QR codes via /api/v1/qr - create, li

2026-08-21 原文 →
AI 资讯

Powerful regression tests for your PostgreSQL project

Mark (aka Winsaucerer) here to show you how you can test your PostgreSQL database like a sorcerer. We are going to be using Spawn, a SQL build system supporting migrations and testing. You do not need to be using Spawn for migrations in order to use it for testing. Spawn does not require any extension installed. All you need is the spawn CLI and a psql connection to the database for Spawn to connect through. Spawn was built to solve some migration pains I've experienced, but I happily discovered that when used for testing, it is very powerful. To show you some of that power, we're going to use a contrived database example. It uses golden file testing to determine success. When the test runs, we capture the stdout and stderr output from psql, and compare that to expected output. Testing with Spawn involves these steps: Create a new test with spawn test new <name> and fill out the test steps Check test outputs with spawn test run <name> (or view the SQL that will be sent to psql via spawn test build <name> ) When outputs are as expected, create the golden file with spawn test expect <name> Run the test and compare to expected output with spawn test compare <name> For now, Spawn only supports connecting via psql, which means that you have access to all the features that psql provides. To get started, follow the Spawn install instructions: Install Spawn And then create a new folder on your system, and initialise a new project with a docker compose config ready for us to play with: # inside your new folder: spawn init --docker docker compose up -d You now have a running docker based PostgreSQL database and a spawn.toml file configured to connect to it. We are not assuming that you are using Spawn or any other tool for migrations, so you can manually create and update the database by connecting directly using psql: docker exec -ti postgres-db psql -U postgres Create the database ⚠️ Caution This post is not intended as an example of how to build an orders database. The des

2026-08-21 原文 →
AI 资讯

Code Smell 321 - Getter Piggybacking

One broken window invites another TL;DR: Don't reuse an existing getter to bolt on new business logic from outside the object. Problems 😔 Duplicated business rules Broken encapsulation Scattered comparison logic Hidden domain knowledge Fragile refactoring Law of Demeter violation Solutions 😃 Add real behavior methods Keep comparisons inside object Pass collaborators, not primitives Reserve getters for rendering Follow tell, don't ask Refactorings ⚙️ Refactoring 027 - Remove Getters Maxi Contieri Maxi Contieri Maxi Contieri Follow Apr 18 '25 Refactoring 027 - Remove Getters # webdev # programming # beginners # java 3 reactions Add Comment 17 min read Refactoring 013 - Remove Repeated Code Maxi Contieri Maxi Contieri Maxi Contieri Follow Jun 16 '24 Refactoring 013 - Remove Repeated Code # webdev # beginners # programming # tutorial 2 reactions Add Comment 3 min read Context 💬 An object exposes a getter for one legitimate reason: some other part of the system needs to read that value, usually to display it. Getters are a code smell, but this one gets a pass, for now. Later on, you discover that you need new business logic that depends on the same value. You already have the getter, so you write a function outside the object that calls it and does the comparison itself, breaking the encapsulation principle. Someone else needs slightly different logic based on the same value. They also call the getter and write their own version of the comparison. Now two places decide what that value means , and neither of them is the object that owns it. Typical. You didn't add a second getter this time. You reused the first one, because it was already there. That's the trap. The getter existed for one reason, and you let it justify skipping the real fix: a method on the object that answers the question itself, instead of handing out the raw value for every caller to interpret on their own. Sample Code 💻 Wrong 🚫 // Food needs to show its use-by date on the shelf // label, so useByDate(

2026-08-21 原文 →
AI 资讯

Detecting Tool + Schema Drift in a Remote MCP Server

An MCP server can ship a change that breaks every agent calling it, and nothing in your monitoring will notice. The endpoint still answers 200. The initialize handshake still completes. tools/list still returns a result. Every signal a conventional uptime check knows how to read stays green — and an agent that memorized last week's tool contract starts failing anyway, because the contract underneath it moved. This is drift: a server's tool inventory or a tool's input schema changing between two points in time, with no transport-level symptom at all. Drift is not a hypothetical. MCP servers are young, most are maintained by small teams, and a tool's inputSchema is just a JSON object in a deploy — there is no compiler stopping someone from renaming a required field, tightening an enum, or dropping a tool nobody remembered an agent still called. The only way to catch it is to have looked at the server before and remember what you saw. What Actually Counts as Drift Drift is anything about a server's advertised capability contract that differs from the last time you checked. Concretely: A tool disappears. It was in yesterday's tools/list , it is not in today's. Any agent that calls it now gets a JSON-RPC error mid-flight, not at startup — the failure shows up wherever the agent happens to reach for that tool. A tool appears. Informational on its own, but worth recording — it is also how you notice a server quietly forking its capability set per client or per deploy. A tool's contract changes shape. Same name, different inputSchema — a field renamed, a type narrowed, a new required parameter, a changed description that alters how an LLM decides to call it. The tool is still callable, which is what makes this the dangerous case: nothing errors immediately, calls just start failing validation or getting silently misinterpreted. The capability set changes. The server stops advertising resources or prompts , or starts. Anything built against the old capability list breaks the

2026-08-21 原文 →
AI 资讯

Your feature-usage scanner doesn't know Vue, Svelte, or Astro exist. Here's how we fixed that without touching its core.

If a static analyzer only walks .ts / .tsx / .js / .jsx , every other file type isn't scanned badly - it's not scanned at all. A .vue component, a .svelte widget, an .astro page: none of them exist to the tool. Not "low confidence." Not "partial support." Invisible, the same way an empty search result looks identical whether there's genuinely nothing to find or the search just never looked in the right place. That's exactly the gap Eventra's CLI had. It scans a codebase and tells you which tracked features are actually used - the whole pitch is "stop guessing which code is dead." Except if your team ships a Vue admin panel, a Svelte checkout widget, and an Astro marketing site around the same core app (which, if you've worked on more than one team, you've probably seen - nobody plans a multi-framework stack, it just accretes), the CLI would silently skip all three, report a clean scan, and never mention that it hadn't actually looked. The exact failure mode the product exists to prevent, happening inside the product itself. We'd already closed this gap once, for Vue. This month we closed it for Svelte and Astro too, and the interesting part isn't the frameworks - it's that adding two more meant touching exactly zero lines of the CLI's core analysis engine. The trick: don't teach the core anything The CLI's core is a TypeScript-compiler-API engine: it builds a real program, walks real ASTs, resolves real symbols across files, and figures out which .track("event_name") calls are statically reachable. It is, deliberately, framework-agnostic - it doesn't know what Vue is, and it shouldn't have to. So instead of teaching the core about .vue / .svelte / .astro , each framework gets a small, separate plugin whose only job is a translation: take the framework file, hand back one virtual TypeScript module. A Vue Checkout.vue becomes Checkout.vue.ts . A Svelte Cart.svelte becomes Cart.svelte.ts . The core never sees the original file - it sees TypeScript, because by the time

2026-08-21 原文 →
AI 资讯

A CSS Hover-Reveal Pattern for Technical Specs

The problem on the Gate Seal page The Gate Seal product page for a maritime client needed to present detailed specifications without turning the layout into a wall of text or a table that looked like an export from Excel. The technical detail buyers cared about was present, but visually buried. The requirement was to surface those details in a compact way, keep the implementation CSS-only, and make sure it still worked with keyboard navigation. The hover-reveal pattern The pattern below uses a hover-reveal on key specification rows. On desktop, moving the cursor over a spec row reveals additional context. With a keyboard, focusing the same row does the same thing. No JavaScript is required for the basic interaction. Structurally, each spec item is a container with two layers of content: Always-visible summary (label and primary value) Hidden detail that appears on hover or focus Here is a simplified version of the markup: <div class="spec-list"> <button class="spec-item"> <div class="spec-main"> <span class="spec-label">Gate size</span> <span class="spec-value">Up to 6 m</span> </div> <div class="spec-detail"> Custom diameters available for retrofit situations. </div> </button> <button class="spec-item"> <div class="spec-main"> <span class="spec-label">Seal material</span> <span class="spec-value">EPDM / NBR</span> </div> <div class="spec-detail"> Oil-resistant compounds for lock gates in heavy traffic.</div> </button> </div> The choice of <button> here is deliberate: it is naturally focusable, works with keyboard navigation, and is announced as an interactive element by assistive technology. In a production implementation, the button semantics can be adapted depending on whether you need a true button or a different element with role="button" . The CSS-only interaction The interaction is controlled through :hover and :focus-visible , with a basic transition for a smoother reveal. .spec-list { display: grid; gap: 0.75rem; } .spec-item { width: 100%; text-align: left

2026-08-21 原文 →
AI 资讯

The Hard Part of Birth Chart Calculations Isn't the Zodiac. It's Time.

The most annoying bugs I’ve dealt with while building a birth-chart engine were not about zodiac signs. They were about time. And the deeper I got into it, the more I realized that “birth time” is a much less simple input than it looks on a form. A local datetime isn't enough Take this: 1990-05-15 09:30 It looks precise. But precise where? Without a timezone, it doesn’t identify an instant. So the calculation API I use takes both the local datetime and the IANA timezone: chart = engine . natal ( local_datetime = " 1990-05-15T09:30:00 " , timezone = " Europe/London " , latitude = 51.5074 , longitude =- 0.1278 , ) I prefer an IANA zone like Europe/London over something like UTC+1 . The former describes a real timezone with historical rules. The latter is just an offset. DST makes this more interesting During a daylight-saving fall-back transition, the same local clock time can occur twice. So a value like: 01:30 may correspond to two different UTC instants. That means the input looks exact to the user while still being ambiguous to the calculation engine. There’s a strong temptation to quietly choose one. I don’t like doing that. If the input is genuinely ambiguous, I’d rather make the ambiguity explicit. The same thing happens in the opposite direction during spring transitions. Some local clock times never existed. If the clock jumped directly from 01:59 to 03:00, then: 02:30 isn’t a valid local instant. Again, silently “fixing” it is convenient. But now the software has changed the user’s data. Then there’s the bigger problem: no birth time A lot of people simply don’t know what time they were born. This creates a product decision. You can say: unknown → 12:00 PM and suddenly everything works. You get: Ascendant houses house cusps Midheaven The object looks complete. But none of those values are based on a birth time the person actually supplied. That bothered me enough that I made unknown birth time a first-class state in the engine. chart = engine . natal ( local

2026-08-21 原文 →
AI 资讯

The Hidden Reasons Your iOS App Feels Slow

An iOS app can feel slow even when its interface looks well-designed and responsive. The problem may not always be the UI or the code running on the device. Often, the real issues are hidden in network requests, API responses, WebSocket connections, and background activity. For developers, finding these problems requires visibility into what is happening behind the screen. This is where Owlse , a network inspection and debugging tool for iOS and macOS developers, can help. 1. What Actually Makes an iOS App Feel Slow? Several hidden network issues can affect an app's performance: Slow API responses Too many network requests Large data payloads Connection delays Failed or repeated requests Background network activity A user may simply see a loading screen or delayed response, while several network operations are happening in the background. Understanding these operations is the first step toward finding the actual cause of the problem. 2. Why Traditional Debugging Can Make These Issues Hard to Find Network-related problems are not always easy to identify through standard debugging. Developers may need to switch between different tools to inspect requests, analyze timing, investigate WebSockets, and understand application issues. When an app generates hundreds of requests, finding one problematic request can also take considerable time. Without a clear view of network activity, developers often have to rely on assumptions. A dedicated network debugging workflow can make this process much easier. 3. Meet Owlse: Network Debugging for iOS & macOS Owlse is built to give iOS and macOS developers greater visibility into their application's network activity. Instead of treating network behavior as something happening in the background, Owlse helps developers inspect and understand it. With features including live request streaming, request inspection, timing analysis, WebSocket inspection, mocking, crash reporting, search, HAR export, and timeline debugging, Owlse brings impo

2026-08-21 原文 →
AI 资讯

The Principle of Least Privilege: Why File Permissions Like 600/644/755 Exist

Anyone who has worked with SSH private keys has run into an instruction to "set it to 600." Config files, by contrast, often get 644, and executable scripts get 755. What do these three-digit numbers actually mean, and why does the right number depend on what kind of file you're dealing with? This post starts from the mechanics of Unix-style (Mac/Linux) file permissions and works up to the design principle behind them: least privilege. Permissions as a 2D grid of who and what Unix-family operating systems express file access as a grid: three kinds of "who" crossed with three kinds of "what." "Who" breaks down into the file's owner, the group the owner belongs to, and everyone else ("other"). "What" breaks down into read, write, and execute. Each cell in that 3×3 grid is either granted or not, and that's exactly what a listing like -rw-r--r-- from ls -l is showing you. Strip the leading character and the remaining nine characters are three groups of three — owner, group, other — each rendered as r/w/x when granted or - when not. Why a single digit can represent read/write/execute Numeric notation like chmod 600 compresses that rwx combination into a single octal digit. Read is worth 4, write is worth 2, execute is worth 1 — powers of two — and you sum whichever bits are set. Note: powers of two are used here because each of read/write/execute is tracked as an independent bit (on or off), and any sum of a subset of {4, 2, 1} maps back to exactly one combination of bits. There's no ambiguity — for example, 6 can only mean read+write (4+2), never any other combination. Read and write, no execute ( rw- ): 4 + 2 = 6 Read only ( r-- ): 4 Read, write, and execute ( rwx ): 4 + 2 + 1 = 7 No access at all ( --- ): 0 A three-digit number like 600 lines up these single digits for owner, group, and other, left to right. 600 means "owner gets read+write, group and other get nothing." What the common numbers actually mean Reading the numbers mentioned at the top through this lens:

2026-08-21 原文 →
AI 资讯

The Active Flag Trap: unvalidated-but-logged-in in CakeDC/Users

If you ship email validation with CakeDC/Users , you eventually hit a question the plugin quietly hands back to you: what should happen when someone registers, never clicks the validation link, and then tries to log in? The honest answer is that CakeDC/Users doesn't decide for you. Out of the box you get a database column, a couple of behaviors, and a set of events — but the experience is yours to assemble. Get it wrong and you land in one of two bad places: a user silently logged in without ever validating, or a user who typed the right password and is told "username or password is incorrect." Neither is what you want. This post walks through why that happens in v16, and a clean way to wire the flow using the events the plugin already dispatches — no core hacks, no schema surgery. One flag, two meanings Everything starts with a single boolean column on the users table: active . When email validation is on, registration creates the account with active = 0 and only flips it to 1 when the user clicks the link in the validation email. You can trace it in BaseTokenBehavior::_updateActive() : // $user['validated'] is a transient flag set to false during register() $emailValidated = $user [ 'validated' ]; if ( ! $emailValidated && $validateEmail ) { $user [ 'active' ] = false ; // registered → inactive + token emailed $user -> updateToken ( $tokenExpiration ); } else { $user [ 'active' ] = true ; // clicked the link → active $user [ 'activation_date' ] = new DateTime (); } Notice there is no separate validated column in the database — $user['validated'] is a transient property used only during registration. The persisted truth is active , and it is doing two jobs at once: "Has this person confirmed their email?" — set by the validation flow. "Is this account enabled?" — the thing an admin toggles to ban or suspend someone. That conflation is the root of everything below. Hold onto it; we'll come back to it. How the finder decides who exists Login in CakeDC/Users runs thro

2026-08-21 原文 →
AI 资讯

Why I Built a Zero-Knowledge, Client-Side Encrypted Burning Note App Over the Weekend

Hey everyone! 👋 Like many developers and sysadmins, I constantly find myself needing to share temporary credentials, API keys, or sensitive text with clients and coworkers. Dropping these straight into Slack, Discord, or standard email always feels like a massive security headache because those chat platforms store everything in plain text in their databases. I looked into popular "one-time secret" web utilities, but I noticed a major flaw: almost all of them handle the encryption and decryption on their servers. That means you have to blindly trust their backend configurations, logging policies, and database security. I wanted something truly zero-knowledge where the server owner physically couldn't read the notes even if they wanted to. So, I built ScorchNote : https://scorchnote.com 🛠️ How it Works (Under the Hood) To achieve absolute zero-knowledge, ScorchNote relies on strict client-side mechanics: Browser-Side Encryption: When you type a secret, the data is encrypted directly in your browser before it ever leaves your network interface. The URL Hash Advantage: The decryption key is generated and stored inside the URL's hash fragment (everything after the # ). Zero Server Footprint: Web browsers never send the hash fragment to the host server during HTTP requests. This means my database only receives a completely scrambled, encrypted payload. The server has no concept of what the key is. Millisecond Burn-on-Read: The moment the recipient visits the link, the encrypted payload is fetched and instantly purged from the server database. 🚀 Try It Out I kept the page entirely lightweight, minimalist, and completely free of bloated tracking scripts. It’s built to do exactly one job, safely and instantly. I would love to hear your thoughts on the architecture, the user experience, or what features you think I should cook up next! Check it out here: ScorchNote

2026-08-21 原文 →