Skip to content
Frontend Feeds
  • Today
  • Archive
  • Sources
  • Categories
    • The Giants6 Sources
    • Multi Author Blogs18 Sources
    • Top Front-end Bloggers25 Sources
    • More Front-end Bloggers77 Sources
    • Browsers, engines, etc.11 Sources
    • Libraries, Frameworks, etc.12 Sources
    • Company/Startup Blogs12 Sources
    • Developer/Designer News4 Sources
    • YouTube Channels13 Sources
    • Podcasts7 Sources

Today

Front-end, in one read.

The latest from the sources worth following, newest first.

Tuesday, 10 November 2026

  • Hacker News: Show HNLibraries, Frameworks, etc.read at source

    Show HN: Clikr – Virtual laser pointer that follows where you point your phone

    Comments

  • DEV CommunityThe Giantsread at source

    Learn Rails 8 by Building Study Flow, Part 2: Authentication with the Built-in Generator

    In Part 1 we generated a Rails 8 app and followed a request from route to controller. Today Study Flow gets its first real feature: users . Before this Rails version, most people added a gem like Devise to get login. Rails 8 ships its own authentication generator . It writes plain Ruby code into your app, so you can read all of it, and that's exactly what we'll do. What you'll learn Running the Rails 8 authentication generator What has_secure_password does for you How a login works: the Session model, signed cookies and Current How allow_unauthenticated_access and rate_limit protect your controllers Adding a signup page, which the generator doesn't create, using params.expect The idea in one minute A website needs to remember who you are between requests, because HTTP forgets everything after each one. The usual trick is a cookie: You log in with email and password. The app creates a session record in the database and puts its id in a cookie. On every later request, the browser sends the cookie back, and the app looks up the session to find the user. Think of a coat check. You hand over your coat (password) once, get a numbered ticket (cookie), and every time you show the ticket, they know which coat is yours. Step 1: Run the generator bin/rails generate authentication bin/rails db:migrate Make sure bcrypt is enabled in your Gemfile (the generator uncomments it, then run bundle install ): gem "bcrypt" , "~> 3.1.7" The generator created a lot. Here's what matters: app/models/user.rb app/models/session.rb app/models/current.rb app/controllers/concerns/authentication.rb app/controllers/sessions_controller.rb app/controllers/passwords_controller.rb app/mailers/passwords_mailer.rb app/views/sessions/new.html.erb app/views/passwords/... db/migrate/..._create_users.rb db/migrate/..._create_sessions.rb Let's go through them. Step 2: The two tables # db/migrate/..._create_users.rb create_table :users do | t | t . string :email_address , null: false t . string :password_digest , null: false t . timestamps end add_index :users , :email_address , unique: true # db/migrate/..._create_sessions.rb create_table :sessions do | t | t . references :user , null: false , foreign_key: true t . string :ip_address t . string :user_agent t . timestamps end Two details are worth noticing: Users store a password_digest , never the password itself. It's a one-way hash. A Session is a database row , one per login, recording the IP and browser. Don't confuse it with Rails' built-in session hash. If you log in on your laptop and your phone, you get two rows, and you can end one without the other. Step 3: The models class User ApplicationRecord has_secure_password has_many :sessions , dependent: :destroy validates :email_address , presence: true , uniqueness: true , format: { with: URI :: MailTo :: EMAIL_REGEXP } normalizes :email_address , with: -> ( e ) { e . strip . downcase } end class Session ApplicationRecord belongs_to :user end Three lines of the User model do a lot: has_secure_password adds password and password_confirmation attributes, hashes the password with bcrypt into password_digest , and gives you authenticate_by and a password reset token. normalizes cleans a value before saving or querying. With this, " Sara@Example.COM " becomes "sara@example.com" , so users can't sign up twice with different capitalization. dependent: :destroy deletes a user's sessions when the user is deleted. You can try it in the console: bin/rails console user = User . create! ( email_address: " Me@Example.com " , password: "secret123" , password_confirmation: "secret123" ) user . email_address # => "me@example.com" user . password_digest # => "$2a$12$..." (a hash, not the password) User . authenticate_by ( email_address: "me@example.com" , password: "secret123" ) # => the user User . authenticate_by ( email_address: "me@example.com" , password: "wrong" ) # => nil Step 4: Current : a request-wide global, done safely class Current ActiveSupport :: CurrentAttributes attribute :session delegate :user , to: :session , allow_nil: true end Controllers, views and models often need to know who's logged in. Current stores the current session for the duration of one request and then resets automatically. That gives you Current.session and Current.user anywhere in your app without passing them around. It's safe because it's cleared between requests. A normal global variable would leak between users. Step 5: The Authentication concern This module is the heart of the system: module Authentication extend ActiveSupport :: Concern included do before_action :require_authentication helper_method :authenticated? end class_methods do def allow_unauthenticated_access ( ** options ) skip_before_action :require_authentication , ** options end end private def authenticated? resume_session end def require_authentication resume_session || request_authentication end def resume_session Current . session ||= find_session_by_cookie end def find_session_by_cookie Session . find_by ( id: cookies . signed [ :session_id ]) if cookies . signed [ :session_id ] end def request_authentication session [ :return_to_after_authenticating ] = request . url redirect_to new_session_path end def after_authentication_url session . delete ( :return_to_after_authenticating ) || root_url end def start_new_session_for ( user ) user . sessions . create! ( user_agent: request . user_agent , ip_address: request . remote_ip ). tap do | session | Current . session = session cookies . signed . permanent [ :session_id ] = { value: session . id , httponly: true , same_site: :lax } end end def terminate_session Current . session . destroy cookies . delete ( :session_id ) end end Read it as a story: included do ... end runs in the controller that includes the module . Adding before_action :require_authentication means every controller is locked by default . That's a safe default: you must opt out, not remember to opt in. require_authentication tries to find the session from the cookie. If there isn't one, it remembers the page you wanted ( return_to_after_authenticating ) and sends you to the login page. cookies.signed means the cookie can't be tampered with. httponly: true hides it from JavaScript. same_site: :lax blocks it on most cross-site requests. start_new_session_for is called after a successful login. It creates the Session row and sets the cookie. after_authentication_url sends you back to the page you originally wanted, or to the root page. ApplicationController includes the concern: class ApplicationController ActionController :: Base include Authentication # ... end Step 6: The login controller class SessionsController ApplicationController allow_unauthenticated_access only: %i[ new create ] rate_limit to: 10 , within: 3 . minutes , only: :create , with: -> { redirect_to new_session_path , alert: "Try again later." } def new end def create if user = User . authenticate_by ( params . permit ( :email_address , :password )) start_new_session_for user redirect_to after_authentication_url else redirect_to new_session_path , alert: "Try another email address or password." end end def destroy terminate_session redirect_to new_session_path , status: :see_other end end allow_unauthenticated_access only: %i[new create] opens the login form and the login action to logged-out visitors. Everything else stays locked. rate_limit is new in Rails 8. It allows 10 login attempts every 3 minutes per IP address, which slows down password guessing. No extra gem needed. User.authenticate_by finds the user and checks the password in a way that takes the same time whether or not the email exists. That prevents attackers from discovering which emails are registered by measuring response times. The error message is the same for a wrong email and a wrong password, on purpose. status: :see_other (HTTP 303) after destroy makes the browser follow the redirect with a GET. This matters with Turbo, which we'll meet in Part 5. Step 7: Password reset (already built) The generator also gave you PasswordsController and PasswordsMailer . The flow: The user enters their email on /passwords/new . If the email exists, the app emails a link containing a signed, expiring token ( user.password_reset_token , supplied by has_secure_password ). The link opens /passwords/:token/edit , where the user picks a new password. On success, all the user's sessions are destroyed , which logs them out everywhere. Notice the response in create is the same whether or not the email exists. Again, that avoids leaking who has an account. We'll look at how mailers work in Part 7. For now just know it works. Step 8: Signup, which the generator doesn't give you The generator creates login but no registration page. Let's add one. Routes ( config/routes.rb ): Rails . application . routes . draw do root "dashboard#show" resource :session resources :passwords , param: :token resource :registration , only: %i[new create] get "up" => "rails/health#show" , as: :rails_health_check end resource :registration (singular) creates /registration/new and POST /registration . There's no id, because a signed-in user has just one of these at a time. We'll compare singular and plural resources in Part 3. Controller : class RegistrationsController ApplicationController allow_unauthenticated_access only: %i[ new create ] rate_limit to: 10 , within: 3 . minutes , only: :create , with: -> { redirect_to new_registration_path , alert: "Try again later." } def new @user = User . new end def create @user = User . new ( user_params ) if @user . save start_new_session_for @user redirect_to after_authentication_url , notice: "Welcome! Your account has been created." else flash [ :alert ] = @user . errors . full_messages . join ( ", " ) render :new , status: :unprocessable_content end end private def user_params params . expect ( user: [ :email_address , :password , :password_confirmation ]) end end Three things to learn here: Signing up logs you in right away. We reuse start_new_session_for , the same method login uses. params.expect is new in Rails 8. Old Rails used params.require(:user).permit(...) . With expect , if the user key is missing or has the wrong shape, Rails answers with a clean 400 Bad Request instead of crashing with an error. It also lists which fields you allow (this is called strong parameters, and it stops users from sending fields like admin: true ). render :new, status: :unprocessable_content . When validation fails we show the form again, with a 422 status. Turbo needs this status to know the form failed, and it won't re-render the page otherwise. View ( app/views/registrations/new.html.erb ). This is trimmed down, and the repo has the full styled version: class= "font-bold text-2xl" > Sign up form_with model: @user , url: registration_url do | form | %> form . email_field :email_address , required: true , autofocus: true , autocomplete: "username" , placeholder: "Enter your email address" %> form . password_field :password , required: true , autocomplete: "new-password" , maxlength: 72 , placeholder: "Enter your password" %> form . password_field :password_confirmation , required: true , autocomplete: "new-password" , maxlength: 72 , placeholder: "Password confirmation" %> form . submit "Sign up" %> end %> link_to "Sign In" , new_session_path %> maxlength: 72 is there because bcrypt only reads the first 72 bytes of a password. Step 9: A page to land on A user who signs up needs somewhere to go, so we add a small dashboard. bin/rails generate controller dashboard show class DashboardController ApplicationController def show end end class= "font-bold text-4xl" > Dashboard button_to "Log out" , session_path , method: :delete %> We use button_to for logout instead of a plain link. A link sends a GET request, but logging out must be a DELETE, and button_to makes a tiny form that sends the right method. Because ApplicationController is locked by default and DashboardController doesn't call allow_unauthenticated_access , visitors who aren't logged in will be redirected to the sign-in page. Try it: Run bin/dev . Visit http://localhost:3000 . You're sent to the login page. Click Sign Up , create an account, and you land on the dashboard. Click Log out , and you're back at the login page. ✅ How to test this Manual checks, as above, plus one extra: after logging in, open your browser's dev tools, then Application → Cookies. You'll see session_id , and its value is unreadable because it's signed. Now automated tests. Create test/models/user_test.rb : require "test_helper" class UserTest ActiveSupport :: TestCase test "normalizes the email address" do user = User . create! ( email_address: " Me@Example.COM " , password: "secret123" ) assert_equal "me@example.com" , user . email_address end test "requires a valid, unique email address" do User . create! ( email_address: "me@example.com" , password: "secret123" ) assert_not User . new ( email_address: "not-an-email" , password: "secret123" ). valid? assert_not User . new ( email_address: "ME@example.com" , password: "secret123" ). valid? end test "authenticate_by accepts the right password only" do user = User . create! ( email_address: "me@example.com" , password: "secret123" ) assert_equal user , User . authenticate_by ( email_address: "me@example.com" , password: "secret123" ) assert_nil User . authenticate_by ( email_address: "me@example.com" , password: "wrong" ) end end Then test/controllers/sessions_controller_test.rb : require "test_helper" class SessionsControllerTest ActionDispatch :: IntegrationTest setup do @user = User . create! ( email_address: "me@example.com" , password: "secret123" ) end test "redirects visitors to the login page" do get root_path assert_redirected_to new_session_path end test "signs in with valid credentials" do post session_path , params: { email_address: "me@example.com" , password: "secret123" } assert_redirected_to root_url follow_redirect! assert_response :success end test "rejects invalid credentials" do post session_path , params: { email_address: "me@example.com" , password: "wrong" } assert_redirected_to new_session_path assert_equal "Try another email address or password." , flash [ :alert ] end test "signs out and removes the session record" do post session_path , params: { email_address: "me@example.com" , password: "secret123" } assert_difference -> { @user . sessions . count }, - 1 do delete session_path end assert_redirected_to new_session_path end end And test/controllers/registrations_controller_test.rb : require "test_helper" class RegistrationsControllerTest ActionDispatch :: IntegrationTest test "creates an account and signs the user in" do assert_difference ( "User.count" , 1 ) do post registration_path , params: { user: { email_address: "new@example.com" , password: "password" , password_confirmation: "password" } } end assert_redirected_to root_path assert_equal 1 , User . find_by! ( email_address: "new@example.com" ). sessions . count end test "re-renders the form for invalid input" do assert_no_difference ( "User.count" ) do post registration_path , params: { user: { email_address: "not-an-email" , password: "password" } } end assert_response :unprocessable_content end end Run them: bin/rails test You'll see them all pass. We don't test rate_limit here because the test environment uses a null cache store, so the counter never accumulates. In Part 12 we'll talk about caches and revisit it. Common mistakes Logout does nothing or raises a routing error. You used link_to ..., method: :delete . Turbo ignores that option. Use button_to , or link_to with data: { turbo_method: :delete } . bcrypt error on user creation. The gem is still commented out in the Gemfile. Enable it and run bundle install . Redirect loops on the login page. A controller that shows the login form must call allow_unauthenticated_access , otherwise it asks for a login to show the login. Recap bin/rails generate authentication gives you users, sessions, login, logout and password reset, as plain readable code. has_secure_password handles hashing; normalizes cleans emails; authenticate_by checks credentials safely. A login creates a Session row and a signed cookie; Current exposes the user for one request. Controllers are locked by default; allow_unauthenticated_access opens specific actions; rate_limit guards against guessing. params.expect is the modern, safer strong parameters. Next up In Part 3 we'll build our first real CRUD: study plans. You'll learn resourceful routes, migrations, associations and validations, and how to make sure users only see their own data.

  • Hacker News Front PageThe Giantsread at source

    The Hardest Tarball?

  • DEV CommunityThe Giantsread at source

    28.6% of small-business sites fire an ad pixel before consent (data from 23,367 scans)

    In a scan of 23,367 small-business websites, more than one in four fired an advertising pixel before the visitor had said anything at all. Short on time? Jump to "How to check your own site" and "How to fix it" near the end: a DevTools checklist and the fix for each of the three setups we see most. Ask a small business owner whether their website tracks visitors, and you will often get a shrug. Ask whether they run ads on Instagram or Google, and the answer is usually yes. Those two answers are more closely connected than most people realize. In October 2026 we scanned the websites of small businesses across seven EU countries. We were looking for trackers that load before a cookie consent choice, or keep loading after the visitor refuses. Analytics showed up everywhere, as you would expect. But the finding that stuck with us was narrower: advertising pixels, the small scripts that report visits and purchases back to ad platforms, were firing before consent on 6,680 of the 23,367 sites we analyzed. That is 28.6%, more than one in four. This piece looks at why that happens, what it looks like on real (unnamed) sites, how to check for it and how to fix it. What an ad pixel actually does An analytics tag answers the question "how is my website doing?" An advertising pixel answers a different one: "which of my ad viewers came here, and what did they do?" When a pixel loads, it typically sends the ad platform a signal that a particular browser visited a particular page, often along with an identifier stored in a cookie. The platform uses that signal to attribute conversions to ads, to build remarketing audiences ("people who viewed a product but did not buy"), and to train the platform's targeting. For an advertiser, this is useful. It is also the textbook example of a non-essential tracker. EU rules require prior consent for non-essential trackers, and regulators expect a visitor to be able to say no before an ad pixel loads, not after. The numbers We looked for the usual advertising tags. Here is how often each one fired before any consent choice: Google Ads/DoubleClick: 4,309 sites Meta Pixel: 3,932 sites TikTok Pixel: 282 sites LinkedIn Insight Tag: 272 sites Pinterest Tag: 241 sites Criteo: 58 sites Two platforms dominate. That is not surprising: Google and Meta are where most small businesses buy ads, and both make their tags very easy to install. The smaller pixels still matter, but they form a long tail. Pixels also travel in groups. On 1,688 sites, both the Meta Pixel and Google Ads fired before consent. Among the 6,680 sites with at least one pre-consent ad pixel, 4,701 had one, 1,610 had two, 305 had three and 64 had four or more distinct ad pixels running before the visitor chose anything. The banner that was never there Here is the part that changes how you think about the problem. Of the 6,680 sites, 4,063 (60.8%) showed no cookie banner at all. That is worth sitting with. For most of these businesses, the issue is not a misconfigured consent tool or a clever dark pattern. There is no consent step whatsoever. The business set up advertising, and nobody set up consent. It is easy to see how that happens. A typical sequence goes something like this: A shop owner opens an ad account to promote a product. The ad platform recommends installing its pixel "to measure results." The website builder or ecommerce platform offers a one-click integration, or the owner pastes a snippet into the site header. Ads start running, conversions show up in the dashboard, and the job feels done. At no point in that flow is the owner forced to think about consent. The pixel works the moment it is installed, and nothing visibly breaks without a banner. So it stays that way. The remaining 39.2% are a different story. These sites did show a banner, but the pixel fired anyway. Here the business has made an effort, and the problem is in the wiring. Three patterns we keep seeing Without naming any site, these are the patterns behind most of the "banner present, pixel fires anyway" cases. The hard-coded pixel. A developer or the owner pasted the pixel snippet into the theme header months or years ago. Later, someone added a consent banner. The banner works perfectly for the scripts it manages, but it does not know the pixel exists, because the pixel never went through it. The banner looks right, and the pixel loads on every page view regardless. The platform integration. Many ecommerce and website platforms offer native "sales channel" or "marketing" integrations that inject ad tags for you. Some of these respect the platform's own consent settings. Some only respect them if those settings are switched on and configured for the visitor's region. Others bypass the site's third-party consent tool entirely. The result can be a banner from one tool and a pixel from another system that never consults it. The tag manager trigger. A tag manager container fires the Google Ads or Meta tag on "All Pages" or on page load. The consent tool sets a consent state, but the trigger does not check it. The banner and the tag manager both work as designed; they are simply not connected. In all three cases, the visible banner gives a false sense of security. The owner sees a banner and assumes the job is done. Platforms: the integration effect Here is the share of analyzed sites on each platform that fired an ad pixel before consent: HubSpot CMS: 57.2% (152 sites) PrestaShop: 55.6% (225 sites) Magento: 53.0% (83 sites) Shopify: 45.1% (1,089 sites) Webflow: 41.0% (212 sites) WordPress: 33.8% (10,609 sites) Squarespace: 26.8% (583 sites) Wix: 13.3% (1,157 sites) A few cautions before reading too much into this. HubSpot CMS, PrestaShop and Magento have small samples, so their exact rates are less stable than WordPress's. And these figures describe the sites in our sample, not what each platform does out of the box. Still, the shape makes sense. Ecommerce-focused platforms sit near the top, because stores advertise and their ad integrations are close at hand. A marketing-automation CMS appears there too, which fits a customer base that runs campaigns. Platforms at the bottom of the list were not free of the problem; their users simply ended up with fewer pre-consent pixels in our scan. The key point is that no platform forces you into this. On every platform we saw sites that handled ad pixels correctly and sites that did not. The difference was configuration. Countries The rate also varied by country: Poland 40.9%, Ireland 36.4%, Portugal 31.4%, Sweden 27.4%, Belgium 27.1%, the Netherlands 26.1% and France 25.2%. We would not draw strong cultural conclusions from this. The samples per country differ in size and in the mix of businesses and platforms. What it does show is that this is not a problem of one market. Even the lowest rate in our sample means roughly one in four sites firing an ad pixel before consent. After Reject Some sites do offer a Reject button on the first layer of their banner. We clicked it, waited, and browsed up to two more pages. On 15.3% of the sites with a first-layer Reject button, an ad pixel still fired after the visitor had refused. The Meta Pixel was the most common survivor, on 381 sites. That fits the hard-coded and platform-integration patterns above: a pixel that never went through the consent tool will not be stopped by it. From the visitor's point of view, this is arguably worse than having no banner. They were offered a choice, made it, and the choice was ignored. Why advertisers should care, compliance aside It is tempting to treat consent as a box to tick for regulators. For advertisers, it is also a data quality issue. When a pixel fires for everyone, the ad platform receives conversions and audience members from people who never agreed to tracking. That data ends up in remarketing lists you should not be targeting and in conversion reports you base budget decisions on. When the setup is later corrected, the numbers shift, and it becomes hard to tell whether a campaign got worse or whether the measurement simply became honest. For Google, there is an additional layer. Since March 2024, Google has required advertisers in the EEA to use Consent Mode v2, which passes the visitor's consent choice to Google's tags. A site that fires Google Ads with no consent signal is not doing what Google itself expects from EEA advertisers. Consent Mode deserves its own article, so we will save the details for that. How to check your own site You do not need special tools for a first look. Open a private window. This gives you a clean slate with no stored consent. Open DevTools before you load the page. Go to the Network tab and tick "Preserve log". Load your homepage and do nothing. Do not click the banner or scroll to dismiss it. Filter the requests. Search for facebook or fbevents , doubleclick , googleadservices , tiktok , linkedin or px.ads , pinterest and criteo . Any match at this stage is a pixel that did not wait. Check cookies. Under Application, look for cookies such as _fbp or _gcl_au . If they appear before you have made a choice, that is another warning sign. Now click Reject. Clear the log, navigate to another page or two, and filter again. Anything that shows up now ignored the refusal. Repeat on a product page or checkout. Some pixels are only added to specific templates. If you use a tag manager, its preview mode will also show which tags fired and on which trigger. Compare that against the consent state at the time. How to fix it The fix depends on which pattern applies to you. If there is no banner at all. Start by adding a consent tool. Then connect every advertising tag to its "marketing" or "advertising" category. A banner on its own does nothing unless the tags actually wait for it. If the pixel is hard-coded. Remove the snippet from the theme or header and reinstall it through the consent tool or through a tag manager trigger that requires marketing consent. Search your theme files for the pixel ID to make sure there is no second copy. If a platform integration injects the pixel. Check the platform's own privacy or consent settings. Many have a setting that tells their integrations to wait for consent, and some only apply it in certain regions. Confirm that the platform's consent signal and your banner agree with each other. If a tag manager fires it. Change the trigger, or add a consent check, so the tag fires only once marketing consent is granted. Test both the accept path and the reject path. If you advertise on Google. Set up Consent Mode v2 with a denied default, and update it only when the visitor accepts. This does not replace a consent gate for other pixels, but it is what Google expects from EEA advertisers. Check each pixel separately. We often see sites where fixing one tag leaves another in place. Fixing Google Ads does not fix Meta, and vice versa. Finally, retest after every change. The most common reason a fix "did not work" is that a second copy of the pixel is still loading from somewhere else. What our scan cannot see A few honest limitations. Our sample came from website links on public Instagram business profiles in seven EU countries: 37,814 domains, of which 23,367 loaded and were analyzed. That skews toward consumer-facing small businesses, the kind that are likely to advertise on social media. It is not a random sample of all websites. We scanned from EU IPs with a real Chromium browser and a clean profile, loading the homepage with no interaction. Some pixels only appear on specific pages, after scrolling or after adding to a cart, so our numbers are more likely to undercount than overcount. Server-side tracking, where the business's own server sends events to the ad platform, is invisible to a browser. A site with no visible pixel may still be sending data that way. And none of this is a legal assessment of any site. We name no websites and make no claim about any individual business. The short version More than one in four small-business sites in our scan fired an ad pixel before the visitor had a say, and six in ten of those had no banner at all. The most common story is not malice or a clever trick. It is a business that switched on advertising and never got around to consent. The good news is that this is one of the easier problems to fix, once you know it is there. The full study is here: small-business cookie consent study . For a shorter, practical version of this article, see Meta Pixel and Google Ads Before Consent . Bannerproof scans websites for trackers that load before consent or after refusal. You can check your own site for free at bannerproof.app .

  • DEV CommunityThe Giantsread at source

    Low impact or medium impact? Where NERC CIP impact ratings go wrong

    The impact rating decides almost everything else in a NERC CIP program: which requirements apply, how much evidence you keep and what an audit looks at. CIP-002-5.1a's bright-line criteria are short, but they are easy to misread. These are the mistakes that come up most for generation operators. 1. Rating the sites and forgetting the control room A single solar or wind plant reaches medium impact under criterion 2.1 only at 1,500 MW or more at one plant location in one Interconnection. That threshold is rarely met, so operators conclude the whole program is low impact. Criterion 2.11 is separate: a Control Center or backup Control Center that performs Generator Operator functions for 1,500 MW or more in a single Interconnection is medium impact. As a portfolio grows, the remote operations center can cross that line while every site stays low. Re-check it every time capacity is added. 2. Treating the rating as a one-time decision CIP-002 R2 requires a review of the identifications at least once every 15 calendar months, with the CIP Senior Manager's approval. New plants, repowering, a backup control center and a change of operator all move the numbers. A rating that was right at registration can be wrong two years later. 3. Building a device list nobody asked for For low impact, CIP-002 R1 asks you to identify each asset — the plant or facility — that contains a low impact BES Cyber System. The standard says a discrete list of low impact BES Cyber Systems is not required. An inventory is still good practice and makes the CIP-003 plan easier to defend. But it should not delay the obligations that are required: the policy, the plan and the controls in CIP-003 Attachment 1. 4. Assuming low impact means little to do Low impact sites still need the CIP-003-9 plan: awareness, physical security, electronic access controls, incident response with a test every 36 calendar months, transient cyber assets and removable media, and vendor electronic remote access controls. Each of these needs evidence that it is in place, not only a written procedure. 5. Ignoring External Routable Connectivity at medium impact Once a system is medium impact, many requirements apply only when it has External Routable Connectivity (ERC) — for example the interactive remote access requirements in CIP-005. CIP-015-1, the internal network security monitoring standard, applies to high impact BES Cyber Systems and to medium impact BES Cyber Systems with ERC. Whether a medium impact system has ERC therefore changes both today's requirements and the monitoring you will need when CIP-015-1 takes effect. Document the routable paths and the reasoning, because the answer is something an auditor will test. What to do this quarter List every control center and backup control center and the MW each one operates, per Interconnection. Put the next CIP-002 review date on the calendar, inside 15 calendar months of the last one. For each medium impact system, record whether it has External Routable Connectivity and why. Check that every low impact asset is covered by a CIP-003-9 plan with all six Attachment 1 sections. How GridShield helps CIPStart's BES impact wizard applies the CIP-002-5.1a bright-line criteria, takes the highest match, and records an approval step. CIPStart's compliance calendar tracks 15-month review cycles. GridShield builds the asset inventory from read-only collection and keeps the evidence hash-chained per asset. Sources NERC Reliability Standards (CIP family) This post first appeared on the GridSafe blog .

  • DEV CommunityThe Giantsread at source

    Spring AI RAG example: index source code with PgVector

    This Spring AI RAG example builds a source-code index with a Spring AI vector store backed by PgVector. It deduplicates files, splits them into embedding-sized chunks, and returns grounded answers with file citations taken from retrieval metadata. Think of it as a local Context7-style index for the library sources your project actually uses, with indexing exposed over MCP. A model trained last year does not know the API you upgraded to last week. By the end, you will have a Spring AI RAG indexer for dependency sources and a chat client that answers from indexed code with file citations. Gradle downloads the source jars, the MCP tool builds the index, and /api/chat asks questions against it. If an outside MCP agent also needs to search or ask, wrap CodeAssistantService.ask(...) in a separate MCP tool. Table of contents How this Spring AI RAG example fits together Deduplicate documents in the Spring AI vector store Deduplication by document hash Deduplication by deterministic id (default upsert) Deduplication by deterministic id (via JdbcTemplate) Chunk documents for Spring AI RAG retrieval Nuances of tokenization and text splitting Splitting text by tokens configuration From index to answers: a grounded chat client How Spring AI QuestionAnswerAdvisor grounds a RAG prompt When the model invents file paths Add file citations to a Spring AI RAG response The client reply differs from the model’s message Where to go next with Spring AI RAG How this Spring AI RAG example fits together The project from part one already has the pieces. It is a Spring Boot RAG application running Spring Boot 4.1 and Spring AI with PgVector. I added a Gradle task, downloadDependencySources , that resolves each runtime dependency’s source jar and unpacks it into sources/ . The flow looks like this: Run downloadDependencySources once to export and unpack your own dependency sources. An indexer walks sources/ , splits each file into chunks, embeds those chunks, and stores them in pgvector. The index_sources MCP tool builds the index, and the /api/chat endpoint asks questions against it. The first index uses two small services: SourcesIndexerService and SourcesQueryService . The MCP tool does not need a separate wrapper class; it lives as an annotated method on the indexer service. Deduplicate documents in the Spring AI vector store Indexing has a problem: you will run it more than once. The app restarts. The source export task runs again. Or you change the embedding model and rebuild the index. If every run inserts the same files again, retrieval quality drops. Top-K results fill with duplicate source files. The database also grows without adding value. Spring AI does not give you one generic “deduplicate this” switch. The VectorStore contract stays small by design. Deduplication belongs to your document identity model. Three options can help: hash checks, fixed ids with PgVector upsert, and a JDBC pre-check before embedding. This demo combines the last two. Fixed text ids keep rows stable. Checksum metadata detects changes. JdbcTemplate skips unchanged files before the embedding call. Deduplication by document hash The first version calculated a checksum for each file, stored that checksum as metadata, and asked the vector store whether a document with that checksum already existed. The query wrapped the same VectorStore . It ran a metadata-filtered search with an empty query, topK(1) , and a zero threshold. That version works across vector stores, but it hides a cost: similaritySearch(...) still asks the embedding model for a query vector before PgVector can run SQL. For an indexer you run often, the dedupe check should not call the embedding model at all. Deduplication by deterministic id (default upsert) The demo solution rests on fixed document identity. A Document created with new Document(text, metadata) gets a random id. Every re-index then creates a new row. A Document created with new Document(id, text, metadata) keeps the same id every time. For source files, the id should come from the file identity, not just the file content. Say you key on the checksum alone. Then two different files with identical content collapse into one document. In the demo I use a readable text id built from the library and the source path: private String documentId ( String library , Path relative ) { String sourcePath = relative . getNameCount () > 1 ? relative . subpath ( 1 , relative . getNameCount ()). toString () : relative . toString (); return library + "/" + sourcePath ; } The current indexer uses that method to build the parent id before it splits a source file into chunks. The exact string format does not matter. The same source file must produce the same id on every run. If the file changes later, the id stays stable. The next run can then replace the content and embedding for that row. One PgVector detail matters here. This Spring AI PgVector example uses readable ids, but Spring AI’s PgVector store defaults to UUID ids. A readable id like library/path needs a text id column: spring.ai.vectorstore.pgvector.id-type = TEXT If you keep the default UUID id type, use a deterministic UUID generator instead of a readable path id. Spring AI’s PgVector store inserts with ON CONFLICT (id) DO UPDATE , so fixed ids make repeat indexing update existing rows instead of piling up duplicates. Without chunking, fixed ids keep one row per source file. In the final chunked indexer, fixed chunk ids make repeat indexing update matching chunk rows. A fixed id alone does not skip embedding. Spring AI computes embeddings before the insert. The id protects the table from duplicate rows. It does not protect the embedding model from repeated work. Deduplication by deterministic id (via JdbcTemplate) For true “do nothing” behavior on unchanged files, make the decision before vectorStore.add(...) . Spring AI’s PgVector store does not expose a switch from ON CONFLICT DO UPDATE to ON CONFLICT DO NOTHING . By the time PgVector runs its insert, Spring AI has already calculated the embedding. The practical version uses a direct id-and-checksum lookup with JdbcTemplate . If the stored row has the same id and checksum, return before vectorStore.add(...) . If no row matches, or the checksum changed, call vectorStore.add(...) and let PgVector upsert the row. @Service @RequiredArgsConstructor public class SourcesQueryService { private final JdbcTemplate jdbcTemplate ; public boolean isDocumentStoredByIdAndCheckSum ( String id , Long checkSum ) { return Boolean . TRUE . equals ( jdbcTemplate . queryForObject ( """ select exists ( select 1 from public.vector_store where id = ? and metadata ->> 'checksum' = ? ) """ , Boolean . class , id , checkSum . toString ())); } } Then the current indexer can skip unchanged files before it splits and embeds the source: Long checksum = crc32 ( file . toAbsolutePath ()); String parentDocumentId = documentId ( library , relative ); String firstChunkId = chunkId ( parentDocumentId , 0 ); if ( queryService . isDocumentStoredByIdAndCheckSum ( firstChunkId , checksum )) { log . debug ( "Source document already stored: {}" , relative ); return ; } This trades portability for behavior this service needs. It depends on PgVector’s table and metadata layout: same id and same checksum: do nothing; same id and different checksum: embed and upsert; missing id: embed and insert. The id lookup already uses PgVector’s primary key. Add metadata expression indexes only for queries that scan by checksum or parent metadata without a specific id. For the demo service, use this version: a cheap JdbcTemplate pre-check plus PgVector upsert for changed files. That tradeoff pays off. One SQL read can save one model call. In the chunked index, each row maps to one chunk: no duplicate rows and no repeated embedding call for unchanged files. Chunk documents for Spring AI RAG retrieval Deduplication decides whether to store a document. Chunking decides the document shape. That boundary matters with source files. Spring AI’s default vector-store batch strategy counts tokens before embedding. It can batch many small documents, but it will not split one oversize file. If a file exceeds the embedding model’s limit, vectorStore.add(...) throws before the file reaches Postgres. You will see this failure: java.lang.IllegalArgumentException: Tokens in a single document exceeds the maximum number of allowed input tokens at org.springframework.ai.embedding.TokenCountBatchingStrategy.batch(TokenCountBatchingStrategy.java:151) ~[spring-ai-model-2.0.0-RC2.jar:2.0.0-RC2] at org.springframework.ai.embedding.EmbeddingModel.embed(EmbeddingModel.java:106) ~[spring-ai-model-2.0.0-RC2.jar:2.0.0-RC2] That error means the batching strategy rejected one Document . A bigger batch size will not help. The document itself has to shrink. The Spring AI ETL pipeline has a tool for this job: TokenTextSplitter . It implements DocumentTransformer . It takes a list of Document objects and returns a new list of smaller Document objects. The split runs before vectorStore.add(...) , so the embedding model sees chunks instead of the original large file. Found this useful? Follow me on social media to stay updated. Telegram · LinkedIn Nuances of tokenization and text splitting TokenTextSplitter runs locally. It does not call the embedding model or send text to an AI server. It uses jtokkit to map text to token ids, takes up to chunkSize tokens, decodes that window, then moves the cut back to the last configured punctuation mark after minChunkSizeChars . Finally, it trims the chunk and drops anything shorter than minChunkLengthToEmbed . Tokenization depends on the model. In this demo the embedding endpoint speaks an OpenAI-compatible API, but the model still runs text-embedding-nomic-embed-text-v1.5 . That API shape does not make the tokenizer OpenAI-compatible. Nomic examples use a BERT tokenizer. Spring AI’s TokenTextSplitter uses com.knuddels:jtokkit , a Java tokenizer for OpenAI-style encodings, and defaults to CL100K_BASE . Spring AI can switch among supported OpenAI-style encodings, but none matches a true BERT tokenizer. When the target model uses another encoding supported by jtokkit , set the encoding type on the TokenTextSplitter builder. For Nomic, this does not give exact counts. A different EncodingType will not close the gap because Nomic uses a different tokenizer family. If exact token accounting matters, swap in a custom DocumentTransformer . Back it with a Nomic/BERT-compatible tokenizer. Until then, TokenTextSplitter still works as a safe local splitter. The local token count gives an estimate. I treat chunkSize as a safety budget, not the model’s real limit. The exact endpoint limit depends on the local embedding server configuration. Chunks around 1000 tokens leave room for batch overhead. Exact counts need a Nomic/BERT-aware splitter or token counter. Splitting text by tokens configuration For source code, I do not want chunks close to the endpoint limit: the batching strategy reserves space, Nomic token counts remain approximate here, and code has dense punctuation. A chunk size around 1000 tokens leaves room for the embedding request while keeping methods and classes searchable. Because the chunk limit belongs to the embedding model, configure the splitter as a shared bean rather than per-method setup. Spring AI will not apply this bean for you inside VectorStore.add(...) . The app still has to call the splitter in its indexing pipeline. One shared bean gives the project one chunking policy. @Configuration public class SourcesIndexingConfiguration { @Bean public TokenTextSplitter tokenTextSplitter () { return TokenTextSplitter . builder () . withEncodingType ( EncodingType . CL100K_BASE ) . withChunkSize ( 1000 ) . withMinChunkSizeChars ( 400 ) . withMinChunkLengthToEmbed ( 10 ) . withMaxNumChunks ( 10000 ) . withKeepSeparator ( true ) . withPunctuationMarks ( List . of ( '\n' , ';' , '}' )) . build (); } } This still does not parse Java. It can split inside a method, a class, or a multiline expression. The project still gets a token safety layer: it keeps line breaks, prefers line or statement boundaries, and keeps ordinary source files well under the local embedding limit. maxNumChunks does not provide safe truncation. If you set it too low for a generated file, the final chunk can still contain the rest of the file and still trip the batching error. To reject generated files, do it at the file boundary by size, extension, or path. Chunking changes document identity. Before chunking, the deterministic id belongs to the source file. From here I call it the parent id. After chunking, the vector store stores chunk rows, and each one must still point back to the same source file. String parentDocumentId = documentId ( library , relative ); Document sourceDocument = new Document ( parentDocumentId , text , Map . of ( LIBRARY_FIELD , library , PATH_FIELD , relative . toString (), CHECK_SUM_FIELD , checksum )); List Document > chunks = tokenTextSplitter . split ( sourceDocument ); This code still lacks full dedupe behavior. Spring AI’s TextSplitter uses the id of sourceDocument as the parent id, copies the original metadata, and adds parent_document_id , chunk_index , and total_chunks to every chunk. We do not have to put parent_document_id into the source document metadata ourselves. TokenTextSplitter creates new Document instances instead of keeping the parent id as the chunk id. Those documents get random ids by default. That works for a one-off index, but it breaks deterministic PgVector upsert: a changed file inserts a fresh set of chunks and leaves the old chunks behind. The parent id only lives in metadata; PgVector’s ON CONFLICT (id) DO UPDATE sees only the row id. Once one source file becomes many vector-store rows, the deterministic id moves from “one id per file” to “one id per chunk”. The file id remains the parent id. Each stored row gets a deterministic chunk id: private String chunkId ( String parentDocumentId , int chunkIndex ) { return parentDocumentId + "#chunk-" + chunkIndex ; } After splitting, assign ids to the chunks. Each chunk needs a distinct deterministic id; otherwise PgVector would collapse the file to one row and each later chunk would overwrite the previous one. In the code below, chunkId(parentDocumentId, index) produces stable ids like library/Foo.java#chunk-0 , library/Foo.java#chunk-1 , and so on. Repeat indexing updates the matching chunk row. List Document > chunkDocuments = IntStream . range ( 0 , chunks . size ()) . mapToObj ( index -> new Document ( chunkId ( parentDocumentId , index ), chunks . get ( index ). getText (), chunks . get ( index ). getMetadata () )) . toList (); vectorStore . add ( chunkDocuments ); Rebuild each chunk with new Document(id, text, metadata) to preserve the metadata TextSplitter added, including parent_document_id . That metadata supports stale-chunk cleanup later. The other option, chunks.get(index).mutate().id(...).build() , carries the metadata for you. But the explicit constructor keeps the storage identity rule visible in one place. I prefer to assign the chunk id in the indexing service. It keeps the storage identity rule next to the checksum and upsert logic. The splitter does not hide it. The checksum story changes too, but only a little. The source file still owns the checksum; chunks inherit that checksum in metadata. A small edit near the top of a file can shift all later chunk boundaries. Chunk-level hashes look less stable than they seem. For this indexer, the file acts as the unit of freshness. The unchanged-file check can still happen before embedding. Do not check the old file row id. Check the first deterministic chunk id plus the file checksum: String firstChunkId = chunkId ( parentDocumentId , 0 ); if ( queryService . isDocumentStoredByIdAndCheckSum ( firstChunkId , checksum )) { log . debug ( "Source document already stored: {}" , relative ); return ; } Checking chunk 0 works for the demo, but it assumes indexing either stored every chunk or failed before storing the first chunk. If you need to tolerate partial indexing, store a manifest row per source file, or split first and compare the stored chunk count with total_chunks . That turns the check into a stronger invariant: the index holds the whole source file at this checksum. If the checksum changed, split the file, assign deterministic chunk ids, and call vectorStore.add(...) . PgVector will upsert chunks whose ids still exist. One cleanup case remains: a file used to produce ten chunks and now produces six. Upsert updates chunks 0..5 , but chunks 6..9 go stale. Delete old chunks for that parent before or after reindexing the changed file. Because the splitter already adds parent_document_id , the cleanup can target the source file with one more JdbcTemplate operation: public void deleteStaleChunks ( String parentDocumentId , Long checksum ) { jdbcTemplate . update ( """ delete from public.vector_store where metadata ->> 'parent_document_id' = ? and metadata ->> 'checksum' is distinct from ? """ , parentDocumentId , checksum . toString ()); } This storage model makes a reliable Spring AI RAG indexer: deterministic identity, checksum metadata, a cheap JDBC pre-check, and PgVector upsert. Chunking only makes the identity model more exact. The file acts as the parent document. The vector-store rows hold chunk documents. For better source-code retrieval later, replace the local TokenTextSplitter setup with a code-aware splitter that understands classes, methods, and declarations. The same storage rules still hold: deterministic chunk ids, file-level checksum metadata, and stale-chunk cleanup when a file changes. With those rules in place, your Spring AI RAG pipeline stays idempotent as dependency sources grow and change. From index to answers: a grounded chat client The index only covers half the system. Now we read from it. In the Spring AI beginner guide , a small @Service wrapped a ChatClient ; then I added a memory advisor and a QuestionAnswerAdvisor . Here we point that same shape at the VectorStore the indexer fills, so the code assistant answers from indexed sources instead of model training data. To make the answer machine-readable, we ask for structured output instead of free text. The reply carries the prose answer, the snippets it leaned on, and the files those snippets came from. public record CodeAssistantReply ( String answer , List String > evidences , List String > absoluteSourceFilePaths ) {} The first service version kept things minimal: wire the two advisors, set a system prompt, and map the model’s JSON onto the record with .entity(...) . That version helps explain the failure, but the checked-in project now uses the grounded version shown below. A thin controller gives the conversation an id when the caller omits one, so memory keeps separate chats apart: @RestController @RequestMapping ( "/api/chat" ) @RequiredArgsConstructor public class ChatController { private final CodeAssistantService codeAssistantService ; @PostMapping public GenerationResponse generation ( @RequestBody GenerationRequest request ) { var conversationId = request . conversationId (); if ( conversationId == null ) { conversationId = UUID . randomUUID (). toString (); } return new GenerationResponse ( codeAssistantService . ask ( conversationId , request . userMessage ()) ); } } Use the checked-in requests/chat-controller.http request unchanged. It asks the assistant about an MCP client: POST localhost:8080/api/chat Content-Type: application/json { "userMessage": "What's mcp client and how to use it?" } The answer looks useful, and the evidences look plausible. Then you check absoluteSourceFilePaths : the files do not exist. The model returned paths it never saw. To understand why, look at what QuestionAnswerAdvisor actually puts into the prompt. How Spring AI QuestionAnswerAdvisor grounds a RAG prompt QuestionAnswerAdvisor packages the retrieval step of Spring AI RAG as an advisor. On every call it does three things before the model runs. First, it searches the vector store with the user’s question as the query: List Document > documents = this . vectorStore . similaritySearch ( searchRequestToUse ); Second, it turns those hits into a context string. This line matters for our bug: String documentContext = documents . stream () . map ( Document: : getText ) . collect ( Collectors . joining ( System . lineSeparator ())); It joins Document.getText() and nothing else. The chunk metadata, including the library , path , and checksum fields the indexer stored, never enters the string. Third, it renders a default template that wraps the question and that context together: {query} Context information is below, surrounded by --------------------- --------------------- {question_answer_context} --------------------- Given the context and provided history information and not prior knowledge, reply to the user comment. If the answer is not in the context, inform the user that you can't answer the question. The model sees code text and an instruction to answer from it, but no file paths. The retrieved documents still exist in Java: the advisor stores them under QuestionAnswerAdvisor.RETRIEVED_DOCUMENTS and copies them to ChatResponse metadata after the call. The path data becomes available after the call, but not in the prompt. When the model invents file paths The bad output has three causes. The model never receives a path. The structured-output converter appends the JSON schema for CodeAssistantReply , so the model sees an absoluteSourceFilePaths field. And the original prompt tells it to fill that field with real file locations. With no path data in context, the model writes plausible strings that point at nothing. First, make the failure visible instead of guessing at it. Three checks, cheapest first. Turn on advisor logging while debugging. SimpleLoggerAdvisor can print the request the model receives, including the augmented user message, so you can confirm the prompt carries code but no paths. Inspect what retrieval returned. Response metadata contains the documents, so you can compare their real paths with the model’s claimed ones. When you add tests, pin the invariant: every cited path must resolve to a real file under the indexed root. If evidence must come from real quotes, add a second assertion that each evidence string appears in the joined retrieved text. Add file citations to a Spring AI RAG response The fix follows from the mechanics: stop asking the model for paths, and read them from retrieval instead. Let the model write the answer and quote evidence. Let the service take paths from the documents the advisor already retrieved. To reach both the parsed entity and the raw response, swap .entity(...) for .responseEntity(...) . That call returns both objects: the converted CodeAssistantReply and the full ChatResponse , whose metadata carries RETRIEVED_DOCUMENTS . @Service public class CodeAssistantService { private final ChatClient chatClient ; private static final String systemPrompt = """ You are a code assistant that explains how libraries and frameworks work. Answer ONLY from the context block provided below the user's question. That context is real source code retrieved from the user's own dependency sources. Rules: - Ground every statement in the provided context. Do not rely on prior knowledge of the library, and do not guess. - In `evidences`, copy short verbatim snippets from the context that back your answer. - If the context does not contain the answer, say so plainly in `answer` and return empty lists. - Leave `absoluteSourceFilePaths` empty. The system fills it from retrieval metadata. Never invent file paths. """ ; CodeAssistantService ( ChatClient . Builder builder , VectorStore vectorStore ) { var chatMemory = MessageWindowChatMemory . builder (). build (); var memoryAdvisor = MessageChatMemoryAdvisor . builder ( chatMemory ). build (); var questionAnswerAdvisor = QuestionAnswerAdvisor . builder ( vectorStore ). build (); this . chatClient = builder . defaultSystem ( systemPrompt ) . defaultAdvisors ( memoryAdvisor , questionAnswerAdvisor ) . build (); } public CodeAssistantReply ask ( String conversationId , String question ) { ResponseEntity ChatResponse , CodeAssistantReply > result = chatClient . prompt () . user ( question ) . advisors ( spec -> spec . param ( ChatMemory . CONVERSATION_ID , conversationId )) . call () . responseEntity ( CodeAssistantReply . class ); CodeAssistantReply reply = result . entity (); if ( reply == null ) { return null ; } List String > realPaths = retrievedPaths ( result . response ()); // Real paths overwrite whatever the model produced. return new CodeAssistantReply ( reply . answer (), reply . evidences (), realPaths ); } private List String > retrievedPaths ( ChatResponse chatResponse ) { if ( chatResponse == null ) { return List . of (); } List Document > documents = chatResponse . getMetadata (). get ( QuestionAnswerAdvisor . RETRIEVED_DOCUMENTS ); if ( documents == null ) { return List . of (); } return documents . stream () . map ( d -> d . getMetadata (). get ( MetadataFields . PATH_FIELD )) . filter ( Objects: : nonNull ) . map ( Object: : toString ) . distinct () . toList (); } } Two changes fix it. The system prompt tells the model to leave absoluteSourceFilePaths empty, which removes the temptation to invent. Then retrievedPaths(...) reads RETRIEVED_DOCUMENTS from response metadata, pulls the path field from each document, and overwrites the field the model returned. The index now supplies the paths as facts, not guesses. One caveat about the field name. The indexer stored PATH_FIELD as relative.toString() , so metadata holds a path relative to the indexed root, not an absolute one. If you want a full path, either store file.toAbsolutePath().toString() at index time, or resolve the relative path against the known sources root when you build the reply. Use this pattern for grounded answers in Spring AI RAG: let the model reason over retrieved text, but take citable facts such as paths, ids, and versions from the retrieved documents themselves. The advisor exposes those documents in response metadata. The mistake comes from asking the model to repeat data the prompt never showed. The client reply differs from the model’s message Precision helps because three versions of the answer exist. The model produces an assistant message: JSON text with answer , evidences , and whatever it guessed for absoluteSourceFilePaths . Inside the call, the advisors run and the structured-output converter maps that text onto a CodeAssistantReply . In ask(...) , your code replaces the paths and builds a new CodeAssistantReply . The controller returns that new record to the client. The model never sees step 3. It only produced the message in step 1. Your code creates the corrected reply after the model finishes and outside its view. That split has one consequence for memory. MessageChatMemoryAdvisor saves history in its after step, and it saves the model’s own output, not your record: assistantMessages = chatClientResponse . chatResponse () . getResults () . stream () . map ( g -> ( Message ) g . getOutput ()) . toList (); this . chatMemory . add ( conversationId , assistantMessages ); Chat memory keeps the message from step 1. The user saw one version, but the next turn remembers another. The refined system prompt makes memory less risky because it tells the model to leave absoluteSourceFilePaths empty. The broader rule still matters. Paths, redactions, reranked evidence, and other post-call changes exist only in the reply you return. If later turns must reason over that corrected reply, store the corrected assistant message under the same conversation id. Keep the two roles apart. The model writes the answer from context. Your service owns the final reply and what gets stored for later turns. Where to go next with Spring AI RAG You now have a repeatable Spring AI RAG pipeline: export source jars, skip unchanged files, split the files that changed, store deterministic chunks in PgVector, and take citations from retrieval metadata instead of model output. For the engineering workflow around it, read AI Coding Workflow: TDD, Reviews, and Guardrails , see where automated reviews still fall short in AI Code Review: 7 Limitations I Found in Production , or browse all artificial intelligence articles . New to Spring AI? Start with Spring AI for beginners: build your first AI app in Java . Found this useful? Follow me on social media to stay updated. Telegram · LinkedIn

  • DEV CommunityThe Giantsread at source

    Engineering Design Process vs. Scientific Method: What’s the Difference?

    You pitch a science fair project: build something that actually works. The proposal comes back with one note in red ink. “Where’s your hypothesis?” This happens more than you’d think. On a physics teachers’ mailing list, one parent described their daughter wanting to build a guitar amplifier out of transistors. Her teacher shut it down because she couldn’t phrase it as a hypothesis. Elsewhere, a student on the Science Buddies forum got a bad grade on an inclinometer project and was told it was “a demonstration,” not a science project. Building an amplifier is a great project. It just runs on a different process. The scientific method is for answering a question about how the world works. The engineering design process is for building something that solves a problem. Most science fairs accept both, and the biggest one, Regeneron ISEF, scores them on separate rubrics. This post explains the difference between the engineering design process and the scientific method in plain terms, puts them side by side, and helps you figure out which one your project really uses before you’re three weeks in. The short answer The scientific method answers a question. The engineering design process solves a problem. A scientist asks “what happens if…?” and runs a controlled experiment to find out. An engineer asks “how can I make something that…?” and builds, tests and improves a design until it meets goals set at the start. US science standards draw the same line. The Next Generation Science Standards pair the two on purpose: “asking questions” is the science practice, and “defining problems” is the engineering version of that same first step ( National Academies ). So the quickest test is how your project ends. If it ends with an explanation backed by data, that’s science. If it ends with a thing that works, and data showing it works, that’s engineering. The scientific method runs in a line. The engineering design process loops until the design meets its criteria. What is the scientific method? The scientific method is a way to test an idea about how something works, using an experiment fair enough that someone else could repeat it and get the same result. School versions usually have six or seven steps: Ask a question. Make it something you can measure. “Does salt water freeze slower than tap water?” works. “Why is the ocean cold?” is too big for one project. Do background research. Find out what’s already known, so your guess is an educated one. Write a hypothesis. A testable prediction with a reason behind it: “If I add more salt, the water will take longer to freeze, because salt lowers the freezing point.” Run an experiment. Change one thing (the independent variable), measure one thing (the dependent variable), and keep everything else the same. Include a control, like a cup of plain tap water. Analyze your data. Average your trials, make a graph, and look for a pattern. Draw a conclusion. Did the data support your hypothesis? A wrong hypothesis is still a good result if the test was fair. Share your results. That’s your poster, report or presentation. Real scientists don’t march through these in a neat line. A surprising result often sends them back to research or a brand-new question. For a school project, though, judges look for each piece, so it helps to be able to point to every step. At Regeneron ISEF, science projects are judged on things like a question that’s testable with scientific methods, variables and controls that are clearly defined, and results that can be reproduced ( Society for Science ). What is the engineering design process? The engineering design process is a loop for building something that solves a specific problem, then testing and improving it until it meets the goals you wrote down at the start. We walk through every step in our engineering design process guide . Here’s the short version: Define the problem. Who needs what? Write your criteria (what the design must do, with numbers) and constraints (budget, size, weight, time). Research. Look at what already exists and why it falls short. Brainstorm. Sketch at least three ideas that work in genuinely different ways. Choose and plan. Score the ideas against your criteria, pick one, and plan the build. Build a prototype. Start cheap. Cardboard and tape count. Test it. Measure it against your criteria, more than once. Improve it. Fix what failed, then go back to step 5. Share the results. Show the final design, the versions that came before it, and the data. The big swap is in step 1. Instead of a hypothesis, you write criteria. “Keeps an ice pack frozen for 4 hours” instead of “keeps lunch cold.” Some fairs call this your engineering goal . When a parent on the Science Buddies forum asked how to fit their kid’s idea into a hypothesis, one reply pointed out exactly that: some fairs ask engineering projects for a goal, not a hypothesis. ISEF’s engineering rubric reflects this. It looks for a practical need, defined criteria and constraints, alternatives you explored, and a prototype tested in multiple trials ( Society for Science ). Engineering design process vs. scientific method: side by side Scientific method Engineering design process Starts with A question about how something works A problem someone needs solved Your key statement A hypothesis Criteria and constraints (your engineering goal) What you change One variable, with everything else held the same The whole design, version after version What you build A test setup A prototype Counts as success Data that supports or rejects the hypothesis A prototype that meets the criteria Shape Mostly a straight line, ending in a new question A loop of build, test, improve Ends with A conclusion you can explain A working design and the test data behind it First item on the ISEF rubric Research Question (10 points) Research Problem (10 points) Example Does salt water freeze slower than tap water? Design a lunchbox insert that keeps an ice pack frozen for 4 hours The last row is the one to remember. Same kitchen, same freezer, two completely different projects. Where the two overlap They have more in common than the red-ink note suggests. Both need fair tests, repeated trials and written records, and both start with research. Judges also care a lot about whether you can explain your own work, whichever process you used. You can see that in the ISEF rubrics too. The science and engineering versions share the same Creativity & Potential Impact section and the same Presentation section. That’s 55 of the 100 points scored on identical criteria. Most real projects also borrow from both. Engineers run small experiments inside the design loop all the time. Back in high school, my NASA HUNCH team built NANO LAB, an enclosure for growing microgreens in zero gravity. Along the way we had to test capillary materials to find which one moved water best. That was a small experiment sitting inside a big engineering project, and its answer went straight back into the design. It works the other way, too. Scientists often have to build their own equipment before they can run an experiment. The question is still scientific, even if a soldering iron was involved. Which one should you use for a science fair project? Ask yourself one thing: at the end, do you want to know something, or have something? If you want to know something, use the scientific method. If you want to have something that works, use the engineering design process. One question decides it. Then check the rubric before you write your proposal. Your project title usually gives it away. Titles that start with “Which,” “Does” or “How does X affect Y” are science. Titles with “design,” “build,” “make” or “improve” are engineering. Then read your fair’s rules before you write a single word of your proposal. Many regional fairs follow ISEF and have a separate engineering category. Some school fairs only have one rubric, and it’s built around a hypothesis. If that’s your fair, ask the teacher early whether an engineering project is allowed and how it will be graded. Five minutes now beats rewriting your proposal later. Worked example: one topic, two projects Here’s a made-up but realistic example. The topic is keeping a school lunch cold. Same lunch, two different projects. The science version Question: Which lunchbox material keeps an ice pack frozen the longest? Hypothesis: The insulated lunchbox will keep the ice pack frozen longest, because its foam lining slows heat transfer. Experiment: Three lunchboxes (fabric, hard plastic, insulated), the same ice pack size, the same room. Check every 30 minutes. Three trials each. Result: A graph of melt time by material, and a conclusion about why. The engineering version Problem: Lunch sits in a backpack for hours before the lunch period, and the ice pack is mush by then. Criteria: Keeps an ice pack at least half frozen for 4 hours and fits inside a standard lunchbox. Constraints: Under $10 in materials, under 150 grams, done in four weeks. Process: Brainstorm three inserts (foam, reflective liner, double wall), score them, build the winner, test it three times, improve it, test again. Result: A working insert, photos of every version, and test data showing it hits 4 hours. The science version teaches you something about materials. The engineering version leaves you with a thing you can put in your backpack tomorrow. Both are good projects. They just get judged on different things. Common mistakes Forcing a hypothesis onto a build. “I predict I can build a guitar amp” isn’t really a hypothesis, and judges can tell. Write clear criteria instead, and say plainly that it’s an engineering project. Building something and never testing it. In one online thread, someone remembered a classmate who built a computer for the science fair. The teacher called it an expensive Lego set and gave it an “adequate” ribbon. An engineering project without test data looks like assembly. ISEF specifically wants a prototype tested in multiple trials. Vague goals. “Strong,” “fast” and “cheap” can’t be tested. “Holds 5 kg,” “under 10 seconds” and “under $10” can. Testing once. One good run might be luck. Repeat every test at least three times, whichever process you use. Hiding the failed versions. In an engineering project, a prototype that broke is proof you actually ran the loop. Put it on your board. Checking the rubric the week before the fair. By then it’s too late to switch processes. Look at it before you pick your topic, and build your science fair timeline around it. Doing it in Flowboard Here’s how we’d set up either process in Flowboard. Each step becomes a card on one canvas with an owner and a due date, so a student, a parent helping out, or a whole team can see what’s done and what’s next. Visual project planning : lay out the seven science steps or the eight engineering steps left to right, so you always know which phase you’re in. Gates : hold “Run the experiment” until the hypothesis and materials are approved, or hold “Build the prototype” until the design is chosen. Sub-boards : give each trial round or each prototype version its own board, so the failed versions stay on record. Project documents : attach the fair’s rubric, your data tables and your photos to the step they belong to. If you’re planning a science fair project now, try Flowboard free for 14 days and set up your board in a few minutes. FAQ What is the difference between the engineering design process and the scientific method? The scientific method answers a question about how the world works, while the engineering design process solves a problem by building something. Scientists test a hypothesis with a controlled experiment. Engineers test a prototype against criteria and constraints, then improve it. Science ends with a conclusion. Engineering ends with a working design and test data. Can a science fair project use the engineering design process? Yes. Most science fairs accept engineering projects, and Regeneron ISEF judges them on a separate engineering rubric. Instead of a hypothesis, you define a problem, set criteria and constraints, build a prototype and test it in multiple trials. Some school fairs only use a scientific method rubric, so check your fair’s rules first. Does an engineering project need a hypothesis? No, an engineering project usually doesn’t need a hypothesis. It needs an engineering goal: clear criteria for what the design must do and the constraints it has to work within. If your teacher’s template still asks for a hypothesis, ask whether you can swap in your criteria, or treat one test inside your project as a small experiment. Is the engineering design process the same as the scientific method? No. They’re related, but they start in different places. The scientific method starts with a question and a hypothesis. The engineering design process starts with a problem and a set of criteria, then loops through building, testing and improving. Both rely on research, fair tests and careful records, which is why they’re easy to mix up. What are some examples of engineering design science fair projects? Good engineering science fair projects solve a small, real problem you can test at home. Examples include a lunchbox insert that keeps ice frozen for four hours, a water filter for muddy water, a bridge that holds the most weight per gram, or a phone stand made from recycled parts. Each one needs measurable criteria. Originally published on the Flowboard blog .

  • Christian HeilmannTop Front-end Bloggers3 min read

    Pardon my dust 2 – redesigning christianheilmann.com

    OK, the shift from WordPress and Godaddy is happening. I managed to convert most of the old blog to static pages and you can see the result already at the old domain Wait-till-i.com It’s a highly responsive and fully static site now, build with lots of dirty PHP conversion scripts and Jekyll. I did use […]

  • AdactioTop Front-end Bloggersread at source

    Six Gutenberg Revolutions in a Row—Asterisk

    An interview with Ken Liu and Ada Palmer: The vast majority of computing power on Earth is human brains, and a huge amount of it is dedicated to worrying about where lunch is coming from tomorrow. If you want to free up computing power, solve poverty. adactio.com/links/22833

  • The Mozilla BlogBrowsers, engines, etc.2 min read

    Firefox’s free built-in VPN uncaps data limit through January 2027

    Whether you’re heading home to see family this winter or taking a well-earned break before the year wraps up, we want you to browse with a little more peace of mind. That’s why, as our gift to you, everyone using Firefox’s free built-in VPN gets unlimited data starting today, November 10, through January 31, 2027. […] The post Firefox’s free built-in VPN uncaps data limit through January 2027 appeared first on The Mozilla Blog .

  • HeyDesignerDeveloper/Designer Newsread at source

    The beige era of product design

    On borrowing Apple's type scale, The new full-stack designer (video), AI prototyping inside your design system, Introducing Adaptive Dark Mode and more.

  • Hacker News Front PageThe Giantsread at source

    Ableton Live 13

  • Frontend Masters BlogMore Front-end Bloggers3 min read

    My Interop 2027 Picks

    What web platform features are we all wishing for perfect cross-browser interoperability next year?

  • Hacker News: Show HNLibraries, Frameworks, etc.read at source

    Show HN: Markview – a fast Markdown reader in Rust, Obsidian Vault compatible

    Comments

  • Hacker News: Show HNLibraries, Frameworks, etc.read at source

    Show HN: Wanim – Run Manim Python in the browser, 20× smaller than Pyodide

    Comments

  • Hacker News Front PageThe Giantsread at source

    Let over Lambda–50 Years of Lisp (2008)

  • Hacker News Front PageThe Giantsread at source

    What's new in the .NET 11 runtime

  • Hacker News Front PageThe Giantsread at source

    AlphaProof Code

  • Hacker News: Show HNLibraries, Frameworks, etc.read at source

    Show HN: Cosh – An open-source AI agent orchestrator

    Comments

  • freeCodeCamp.orgYouTube ChannelsVideoWatch on YouTube

    Codex & AI Coding Agents - Learn Agentic Coding

  • Hacker News: Show HNLibraries, Frameworks, etc.read at source

    Show HN: Eigin. Local-first and no-shell personal AI for iOS

    Comments

  • Flavio CopesMore Front-end Bloggersread at source

    API pricing vs subscription: what my month of Codex would cost on the API

    I priced 30 days of my real Codex usage at API rates. A $100 plan covered $745 of API-equivalent work. Here's the math, the script to run on your own logs, and when the API still wins.

  • HeyDesignerDeveloper/Designer Newsread at source

    AI skills for building better interfaces

    Iconden: The iPhone app icon gallery, "x is dead" is dead, When touch makes sense on a laptop

  • HTML All The ThingsPodcastsAudioListen

    Choosing the Right AI Model for the Job

    Are you wasting money and usage limits by choosing the most powerful AI model for every task? Or are you wasting even more time fighting with a cheaper model that isn't up to the job? In this episode, Matt and Mike discuss when to use smaller, faster AI models and when it's…

  • Chris HawkesYouTube ChannelsVideoWatch on YouTube

    My Dad Died

  • AbduzeedoMulti Author Blogsread at source

    Editorial Design: Typography 47 by Slanted Publishers

    Slanted Publishers and the Type Directors Club reshape Typography 47 with a 17 × 24 cm thread-stitched editorial design and changing papers. For the 47th edition of The World’s Best Typography, edited...

  • SitePointThe Giantsread at source

    How to Build an Accessible Gold Value Calculator in Vanilla JavaScript

    null Continue reading How to Build an Accessible Gold Value Calculator in Vanilla JavaScript on SitePoint .

  • AbduzeedoMulti Author Blogsread at source

    Inside Weekend Vikings’s Web Design for Driftlines

    Driftlines by Weekend Vikings is an interactive WebGPU web design experience where nine jazz tracks are drawn in glowing light strands. Built for Budapest jazz duo Gábor Szunyogh (keys) and Viktor Sza...

  • SitePointThe Giantsread at source

    What Actually Happens When a Kafka Region Fails?

    null Continue reading What Actually Happens When a Kafka Region Fails? on SitePoint .

  • JavaScript WeeklyMulti Author Blogsread at source

    How X (Twitter) is rebuilding its web app

    #​810 — November 10, 2026 Read on the Web JavaScript Weekly How X (Twitter) is Rebuilding Its Web App — X is replacing three servers with one app built on React, Vite and Fastify. Yagiz, famed for his performance work on Node, explains the streaming architecture behind it, along with some bottlenecks they had to fix, like how hundreds of tiny JavaScript files slowed startup. Yagiz Nizipli (X Engineering Blog) Why Standard Webhooks Hit 118m npm Downloads a Month — OpenAI, Anthropic, and Gemini all sign webhooks with Standard Webhooks, the open spec Svix started. That's one finding in the State of Webhooks 2027, where we scored 489 API companies on retries and signatures. Immediate download, no email required . Svix sponsor Parcel 3 Alpha: The Bundler Rewritten in Rust — A ground-up Rust rewrite of the popular bundler that the team reports is up to 100x faster than Parcel 2 at cold builds of React Spectrum. It runs as a standalone binary without Node, with support for native plugins written in Rust, Go or Swift, while JavaScript plugins run on an embedded QuickJS runtime. The Parcel Team IN BRIEF: 📊 The State of JavaScript 2026 survey is now open . Share the features, libraries and tools you use and like, and discover a few new ones along the way. We'll share the results once they're out. TypeScript 7's Go-powered compiler now has an experimental WebAssembly build which already powers an early TypeScript Playground v2 . ESLint now officially lints shell scripts with the @eslint/shell plugin adding ShellCheck-like rules for Bash, sh and mksh. RELEASES: Playwright 1.65 – The browser testing tool's test runner can now run under Bun, plus there's a new failures reporter. Storybook 11 Beta – The UI component workshop gets more agent-ready, with official skills, Claude/Codex plugins, headless CLI tools, and more. 🔒 Vite has disclosed a critical remote code execution flaw affecting dev servers exposed to the network via --host or server.host . Upgrade to 8.3.7, 8.2.5, 8.1.7, 7.3.8 or 6.4.6. 🔒 Next.js 16.4.5 and 15.5.29 fix seven security issues , including a high-severity cross-tenant cache disclosure. 🔒 Node.js security releases are due tomorrow (or shortly after) for the 26.x, 24.x and 22.x lines. 📖 Articles and Videos Experiments with Multithreaded JavaScript — What would JavaScript look like if threads could share objects directly? Former TC39 delegate Erik Arvidsson explores three approaches: in a Porffor fork, as a TypeScript library with an ownership checker, and as an implementation of the TC39 structs proposal . Each has a playground to try. Erik Arvidsson The New JavaScript Features Between V8 14.6 and 15.6 — Node 27, released in alpha recently, is built on V8 15.6, and Joyee tours what that brings to developers, including Math.sumPrecise , new iterator methods, and some behavior changes in sorting, dates and promises, all with sample code. Joyee Cheung How to Build Your Own Automated Debugging Workflow — Connect your coding agent to Sentry and turn errors into fix PRs. See the guides and recipes in the resource hub. Sentry sponsor Spreading Liquid with three.js — A technical explanation of a 'liquid-spreading' effect originally built for a cosmetics site. The in-page demo is fun to play with and this could be handy if you want to create paint-like effects. Misaki Nakano Reviving Facebook's Abandoned JavaScript Optimizer — Prettier creator James Long put agents to work resurrecting Prepack , Facebook's attempt to make JavaScript faster by running it ahead of time. There's an experimental demo that shows off the gains on a sample React app. James Long Yavashark: Building a JavaScript Engine for 950 Days Straight — A solo developer's progress report on a Rust-based engine they're building that passes around 82% of test262 and can server-render a React app. There's a playground to try it for yourself. Sharktheone 📄 A Fifth Object Shape Made the Same Loop Twice as Slow – V8's hidden classes and inline caches in action. Mohsen Fallahnejad 📺 ReactiveScript: Early Findings – A lengthy stream by Solid's creator on ReactiveScript , his experimental reactive UI language he first shared in 2021 . Ryan Carniato 🛠 Code & Tools EmbedPDF 3.0: A PDF Viewer for Any JavaScript App — An open-source PDF viewer for vanilla JavaScript, React, Vue, Svelte and Angular. The v3 rebuild offers a ready-made viewer, headless components for custom UIs, and a standalone PDF engine. Try the demo. EmbedPDF Embed an Office Editor Your App's AI Agent Can Use — DOCX/XLSX/PPTX editing in your app with your model and workflow. Agents make targeted edits, and users refine the file. Thinkfree sponsor chrome-capture: Record a Chrome Tab at 60-120fps to an MP4 File — It's been three years since we've linked to Paul Irish (once a frequent fixture in JavaScript Weekly!) but he's back with a neat, compact tool you can run with npx to record a Chrome tab at high quality including audio to an MP4 video. Paul Irish Text.js: React Apps That Render in the Browser Console — Really! A React 19 framework whose 'screen' is the DevTools console, with routing, keyboard input, layout primitives and ASCII fonts. Open the console on the homepage to try it, then bunx create-text-app if you're tempted. GitHub repo. Anomaly Jeasx 3.0: Server-Rendered JSX, Now Streaming — A minimal framework that uses JSX purely as a server-side templating language (with no React and no hydration) on top of Fastify and esbuild. v3 streams pages while async components are still loading and renders ~2x faster. Live examples. Jeasx Rolldown 1.3 – The Rust-powered bundler adds experimental support for import attributes, but there are breaking changes to be aware of. jsdom 31.0 – The pure JS implementation of DOM/HTML standards adds a new CSS engine and switches to a Skia-backed canvas backend. Remix 3.2 – The full-stack framework adds nonce-based Content Security Policy middleware. Wiki.js 3.3 – Open source JavaScript and Node.js-powered wiki app. 📰 Classifieds 📺 JetBrains AI.js Day 2026 : Frontend's scariest AI questions answered live on channel 13, Nov 13. Register now! 🔑 One subscription, a dozen people. Fingerprint shows how many devices are really behind each account. Try it free . 📢 Elsewhere in the ecosystem 📺 I've been enjoying Arjay McCandless's YouTube channel lately as he's been focusing on maxing out VPSes with different stacks (and often being surprised by the results). This time, he compares 22 combinations of JavaScript runtimes and frameworks to see how many users they can handle! 🌙 Safari 27.3 beta introduces Adaptive Dark Mode , an opt-in feature that automatically darkens light websites that don't provide their own dark theme. 🎨 Three.js creator mrdoob is posting daily visual experiments for Codevember , complete with live demos and source. 💰 Cloudflare has handed out the first $301,300 of the $1M Vite Ecosystem Fund it pledged when VoidZero joined, spread across 44 projects and maintainers, including e18e.

Monday, 9 November 2026

  • AbduzeedoMulti Author Blogsread at source

    Madeleine: Tactile Butter Packaging Design by Studio Hi Ho

    Studio Hi Ho pairs waxed brown kraft paper, butter-yellow labels, and 1948 Highway Gothic in its packaging design for Melbourne’s Madeleine. According to founder Jack Gaffney, Melbourne butter maker M...

  • Jens Oliver MeiertTop Front-end Bloggersread at source

    How to Create, Edit, and Validate AWAGAM Blocklists

    Extending the tooling for the AWAGAM request blocker, a new editor is available to set up and test blocklists and blocklist bundles.

  • AdactioTop Front-end Bloggersread at source

    Monday session

  • AbduzeedoMulti Author Blogsread at source

    Branding: Remote brand identity by COLLINS

    COLLINS rebuilds Remote around a stackable cube and banknote colors, turning global payroll into a Remote brand identity with real weight. Remote hires and pays people in more than 150 countries, in 7...

  • AbduzeedoMulti Author Blogsread at source

    Typography: Setouchi Sans Custom Font by Blaze Type

    Blaze Type adapts its Osmose typeface with a custom OU ligature for Setouchi Sans, shaping calm destination typography for Setouchi, Japan. Positioned between electric Tokyo and historic Kyoto, the Se...

  • Level AccessCompany/Startup Blogs11 min read

    Accessibility MCP Servers: What They Are and How They Work

    An accessibility Model Context Protocol (MCP) server connects AI assistants to testing engines, Web Content Accessibility Guidelines (WCAG) guidance, or existing findings, so The post Accessibility MCP Servers: What They Are and How They Work appeared first on Level Access .

  • Engineering at MetaCompany/Startup Blogs6 min read

    Extending LLDB to Debug Applications That Span CPUs and GPUs

    We built a vendor-neutral accelerator plugin architecture in lldb-server for debugging applications that span CPUs and GPUs. It separates shared debugger infrastructure from hardware-specific device control. The design represents the CPU and GPU as separate debugger processes while presenting them in one debugging session. Modern AI applications rarely execute in one place. A workload can [...] Read More... The post Extending LLDB to Debug Applications That Span CPUs and GPUs appeared first on Engineering at Meta .

  • Microsoft Edge BlogBrowsers, engines, etc.5 min read

    Shop faster and save money with built-in tools in Microsoft Edge

    It's 11 p.m. Fifteen tabs are open, the reviews don't agree, and you've checked the price three times. Then, just when you think you're done, a promo code box appears with a code you don't have. So, you keep shopping, long past the point you wanted t The post Shop faster and save money with built-in tools in Microsoft Edge appeared first on Microsoft Edge Blog .

  • AbduzeedoMulti Author Blogsread at source

    Porto Rocha Unveils Fursys Brand Identity

    Porto Rocha rebuilt Fursys, the Korean workplace maker founded in 1983, around one idea: a Fursys brand identity that makes room for reinvention. Fursys spent four decades shaping the modern workplace...

  • CSS-TricksThe Giantsread at source

    CSS-Tricks, Version 20

    Things look a little different around here? I hope so because we just pushed a site redesign! Here's everything new with the 20th(!!) version of CSS-Tricks. CSS-Tricks, Version 20 originally handwritten and published with love on CSS-Tricks . You should really get the newsletter as well.

  • AbduzeedoMulti Author Blogsread at source

    Halo Lab’s Web Design for Bella Branding, App & Website

    Bella Branding, App & Website by Halo Lab is a web design project. See how Halo Lab built branding, an app, admin panel and website for Bella. Bella Branding, App & Website Design for AI EdTec...

  • AbduzeedoMulti Author Blogsread at source

    Akronim Design Bureau’s Brand Identity for Matter Cine Week '26

    Matter Cine Week '26 by Akronim Design Bureau is a brand identity project exploring camcorder optics, pixel geometry, and expressive print. Akronim Design Bureau built the visual system for Matter Cin...

  • David BushellMore Front-end Bloggers4 min read

    I was wrong about dark mode

    Apple invented dark mode again! They’re calling the new feature Adaptive Dark Mode™. I’m learning that other browsers already had a similar feature. I assumed people relied on add-ons like Dark Reader (and millions do). Regardless, pushing what is effectively an accessibility option to the forefront […]

  • Frontend Masters BlogMore Front-end Bloggers7 min read

    TanStack AI’s Sandbox API

    Part 3 of this intro to TanStack AI covers the Sandbox API: spinning up a remote sandbox on Vercel or Railway, running Claude Code inside it with GitHub access, and having it clone your repo, add a feature, and open a pull request.

  • SyntaxPodcastsAudioListen

    1054: Reddit Closing it’s Doors

    Wes, Scott, and CJ are back with two weeks of news: Reddit is forcing everyone to log in, r/opensource bans anything AI, and CSS-Tricks is independent again. Plus Anomaly's Hex dictation app, Mitchell Hashimoto's Rex terminal, TanStack Redact, Cloudflare's TypeScript port to…

  • PiccalilliMore Front-end Bloggersread at source

    JavaScript for Everyone: New Game+

    We have great news. After months of effort, 3 new modules, 13 completely re-worked lessons and 20 new lessons (including practical exercises), JavaScript for Everyone's New Game+ update is now live! Check it out! We took feedback from the community about what they thought was missing from the course and overwhelmingly, people wanted practical exercises. It makes sense: JavaScript is a complex language to learn and there's not many better ways to cement new knowledge than by actually building with it. Mat took this feedback as a challenge, formulated a plan and got to work , disappearing into a text editor and reappearing with pretty much a whole course's worth of new and updated material. It's really inspiring to me, personally, because doing anything while it felt like our industry was being decimated felt impossible. Mat's resolve to keep doing what he does well: teach people, has been unmatched and the benefactors of that effort are everyone . JavaScript for Everyone was already the canonical, high quality educational resource for learning JavaScript, but now, it's at a whole new level. So what are you waiting for? Take it for a spin and truly understand how JavaScript works! Check it out!

  • SitePointThe Giantsread at source

    Why Transactional Emails Fail After Deployment: A Practical Debugging Guide for Developers

    null Continue reading Why Transactional Emails Fail After Deployment: A Practical Debugging Guide for Developers on SitePoint .

  • Flavio CopesMore Front-end Bloggersread at source

    AI fundamentals: a beginner's guide to language models and coding agents

    How AI models and coding agents work, shown with real runs: the words, tokens, the context window, why models make things up, thinking models, RAG, choosing a model, the agent loop, permissions and checking the work.

  • SitePointThe Giantsread at source

    Building Reliable Product Emails Into Your Application

    null Continue reading Building Reliable Product Emails Into Your Application on SitePoint .

  • Net NinjaYouTube ChannelsVideoWatch on YouTube

    GitHub Copilot App Tutorial #10 - Making a Custom Canvas

  • HeyDesignerDeveloper/Designer Newsread at source

    The beige era of product design

    Why I make mood boards, By taking away, Critical thinking when agents do the work, WebMCP, explained with a to-do app.

  • SitePointThe Giantsread at source

    How Text-to-Image AI Generators Work, and 5 Tools That Deliver Good Results

    null Continue reading How Text-to-Image AI Generators Work, and 5 Tools That Deliver Good Results on SitePoint .

  • SitePointThe Giantsread at source

    How to Reduce Hallucinations in LLMs

    null Continue reading How to Reduce Hallucinations in LLMs on SitePoint .

  • David Walsh BlogTop Front-end Bloggers2 min read

    STRG: S3-Compatible, AI-Native Cloud Storage (Sponsored)

    Asset management is big business within the web world. The larger that our web apps grow, the larger the number of assets and their file size naturally increases. With that increase, you need to worry about increasing costs and vendor complexity! Luckily I’ve discovered a service which fixes all of those problems! Welcome to STRG: […] The post STRG: S3-Compatible, AI-Native Cloud Storage (Sponsored) appeared first on David Walsh Blog .

  • Auth0 BlogCompany/Startup Blogsread at source

    Authentication and Authorization: What's New in .NET 11

    Learn what’s new in .NET 11 regarding authentication and authorization.

Sunday, 8 November 2026

  • SitePointThe Giantsread at source

    What AI Gets Wrong About the Front End, and Why You Still Need to Know

    null Continue reading What AI Gets Wrong About the Front End, and Why You Still Need to Know on SitePoint .

  • Jim Nielsen’s BlogMore Front-end Bloggersread at source

    Addition By Subtraction

    I still think saying no is important. In that vein, here’s something I shipped by removing functionality (and I think my site is better for it). For the longest time on my icon gallery sites , you could view a list of icons in four pixel dimensions: 56×56 128×128 256×256 512×512 Each of these sizes could be accessed via a control on the website. This has been true for years on my site, whether it’s the current site: Or an older version of the site: By default, I loaded the 128-pixel version of the icons. But if you wanted to see other sizes, you could. However, the more I used the site myself the more I thought, “Four options is too many”. When it comes to choice, what I really wanted was: Default Smaller Bigger (Plus three is always a good number , unless you have a reason otherwise). So I removed the big 512 option. If I want to study an icon at that size and detail, I click through to the icon’s detail page. Now there are three options for list views: 56, 128, and 256. Small (great on mobile), 128 (great by default), and big (let me see more detail). It’s simple to think about: You get a size by default. Not big enough? Go up a size. Too big? Go down a size. You don’t need to think about it anymore than that. I mean you can , but I’ve found I don’t need to. So I’ve taken that off your plate. Less cognitive overhead. Maybe one day I’ll add a slider and make it all dynamic. But for now, I like this small addition by subtraction. Reply via: Email · Mastodon · Bluesky

  • Stéphanie WalterMore Front-end Bloggersread at source

    Pixels of the Week – November 8, 2026

    This week we explore if AI could make the open web disappear, if a dialog should close on outside click (plot twist: it depends), and we tag along flights in the sky. And don’t miss the fun pigeon armchair and portraits of people who dress alike.

  • SitePointThe Giantsread at source

    Friendly Fraud Prevention for Developers: What to Log, Block and Automate

    null Continue reading Friendly Fraud Prevention for Developers: What to Log, Block and Automate on SitePoint .

  • AdactioTop Front-end Bloggersread at source

    Lunch on the beach in the November sunshine.

See everything from the last 30 days →

Articles belong to their publishers. This site only collects what their feeds provide.

Updated 10 November 2026 at 15:29