Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
154393 stories
·
33 followers

Pope Leo calls for being ‘profoundly human’ in the age of AI

1 Share
Pope Leo XIV attends the presentation of his first Encyclical Letter "Magnifica humanitas" on May 25, 2026 in Vatican City, Vatican. | Getty Images

Pope Leo XIV warned of the risks of AI and unconstrained technological power in his first major papal document released on Monday. Magnifica Humanitas is the pope's manifesto on "safeguarding the human person in the time of artificial intelligence," in which he discusses the dangers of AI-powered warfare, the effects of AI on labor, and the need for new legal and ethical frameworks to govern technology.

In his papal encyclical - a kind of open letter from the Catholic Church - Pope Leo stressed the economic and social upheaval that rapid AI adoption is creating, with inadequate protections for individuals that threaten human dignity. He com …

Read the full story at The Verge.

Read the whole story
alvinashcraft
50 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Cross-Document View Transitions: Scaling Across Hundreds of Elements

1 Share

In Part 1, we covered the gotchas that bite you first: the deprecated meta tag that silently does nothing, the 4-second timeout that kills transitions without telling you, the image distortion that turns every aspect ratio change into silly putty, and the pagereveal/pageswap events that give you hooks into the transition lifecycle.

All of that gets you from “nothing works” to “one element transitioning nicely between two pages.” Which feels great. For about five minutes. Then you try to build a product listing page with 48 cards that each need to morph into a detail view, and you realize the tutorials left out the hard part.

This is where it gets real. Let’s scale this thing.

Cross-Document View Transitions Series

  1. The Gotchas Nobody Mentions
  2. Scaling View Transitions Across Hundreds of Elements (You are here!)

The Dream: One Line, Infinite Names

In a perfect world, you’d solve the scaling problem with pure CSS. No JavaScript. No server-side loops. Just this:

.card {
  /* Generates card-1, card-2, card-3, etc. automatically */
  view-transition-name: ident("card-" sibling-index());
}

That’s ident() — a CSS function proposed by Bramus (who works on Chrome) to the CSS Working Group. It takes strings, integers, or other identifiers, concatenates them, and spits out a valid CSS name. Pair it with sibling-index(), which returns an element’s position among its siblings (1, 2, 3…), and you get auto-generated unique names for every element in a list. One rule. Works for 10 cards or 10,000. The CSS doesn’t care.

And it’s not just view transitions. The same pattern works for scroll-timeline-name, container-name, view-timeline-name — anywhere you need unique identifiers at scale. You could even pull names from HTML attributes with attr() instead of sibling-index(), constructing identifiers like ident("--item-" attr(id) "-tl"). The flexibility is real.

Here’s the thing: half of this equation already exists. sibling-index() shipped in Chrome 138 — you can use it today for things like staggered animations and calculated styles. The missing piece is ident(). There’s a Chrome Intent to Prototype from May 2025, which means it’s on the radar. But “on the radar” and “in your browser” are very different things. No browser ships ident() yet, and there’s no timeline for when it’ll land.

So we can’t use it yet. But it’s worth knowing about because once ident() ships, a huge chunk of the complexity you’re about to see just… evaporates. Until then, here’s how you solve the same problem efficiently today — with the tools that actually exist in browsers right now.

100 Products, 100 Names, 1 Nightmare

Here’s what happens when you follow a tutorial that shows one hero image transitioning between two pages and try to apply that pattern to a grid:

/*  THE NIGHTMARE - one rule per item, forever */
::view-transition-group(card-1),
::view-transition-group(card-2),
::view-transition-group(card-3),
::view-transition-group(card-4),
::view-transition-group(card-5),
::view-transition-group(card-6),
::view-transition-group(card-7),
::view-transition-group(card-8)
/* ... imagine 92 more of these */ {

  animation-duration: 0.35s;
  animation-timing-function: ease-out;
}

::view-transition-old(card-1),
::view-transition-old(card-2),
::view-transition-old(card-3)
/* kill me */ {
  object-fit: cover;
}

That’s what you end up with if you follow the tutorials that only show one or two named elements. They assign view-transition-name: hero to one image and call it a day. Cool. Now try building a product grid.

Every view-transition-name on a page must be unique. That’s a hard rule — if two elements share a name, the browser doesn’t know which one maps to which on the next page, so it throws the whole transition out. On a listing page with 48 products, you need 48 unique names. On a photo gallery with 200 thumbnails, you need 200. The names aren’t the problem — you can generate those. The problem is that every pseudo-element selector in your CSS targets a specific name, so your animation styles explode into an unmanageable wall of selectors.

This is where you need to understand the difference between two properties that sound like they do the same thing but absolutely do not.

Name vs. Class: The Distinction That Changes Everything

And yeah, the naming here is confusing. I’ll be honest: the first time I saw view-transition-name and view-transition-class next to each other, I thought they were interchangeable. They’re not, and the difference matters.

Name = identity. It answers: “Which element on Page A is the same element on Page B?” When you give a thumbnail view-transition-name: card-7 on the grid page and give the hero image view-transition-name: card-7 on the detail page, you’re telling the browser those are the same thing and to animate between them. Names must be unique per page. Two elements can’t both be card-7 or the whole thing breaks.

Class = styling hook. It answers: “How should the animation look?” When fifty elements all have view-transition-class: card, you can write one CSS rule that controls the duration, easing, and object-fit for all of them. It’s the same mental model as CSS classes on regular elements — .btn doesn’t identify a specific button, it says “style me like a button.”

Think of it like a database. The name is the primary key — unique, identifies one specific row. The class is a category column — groups rows together so you can run a query across all of them at once.

Here’s what that looks like in practice:

There it is. Six cards, six unique names, but exactly three CSS rules handling all the animation behavior. Could be sixty cards. Could be six hundred. The CSS doesn’t change.

The key line is that selector: ::view-transition-group(*.card). The asterisk is a wildcard for the name, and .card matches the view-transition-class. It reads as “any view transition group whose element has view-transition-class: card, regardless of what its specific name is.”

For cross-document multi-page application (MPA) transitions, the pattern is the same but you generate the names on the server:

<!-- Page A -->
<div class="grid">

  <!-- ... -->

  <a
    href="/product/42"
    class="card"
    style="view-transition-name: product-42; view-transition-class: card"
  >
    <img src="/images/42-thumb.jpg" alt="Widget" />
  </a>
  <a
    href="/product/43"
    class="card"
    style="view-transition-name: product-43; view-transition-class: card"
  >
    <img src="/images/43-thumb.jpg" alt="Gadget" />
  </a>
</div>
<!-- Page B -->
<div
  class="product-hero"
  style="view-transition-name: product-42; view-transition-class: card"
>
  <img src="/images/42-hero.jpg" alt="Widget" />
</div>
/* ONE stylesheet, shared by all pages, handles every product */
@view-transition {
  navigation: auto;
}

::view-transition-group(*.card) {
  animation-duration: 0.35s;
  animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

::view-transition-old(*.card),
::view-transition-new(*.card) {
  object-fit: cover;
}

That’s the entire animation stylesheet for a site with thousands of products. Three rules. No matter how many items you have in the database, you never add another line of transition CSS.

Before view-transition-class existed, people were doing horrifying things — looping through items in JavaScript to generate <style> blocks with hundreds of selectors, or using CSS preprocessors to spit out every possible name permutation at build time. It worked, technically, the same way duct-taping a car bumper works technically.

view-transition-class is the spec authors acknowledging that the original API just didn’t scale, and fixing it the right way.

One gotcha: view-transition-class was added to the spec later to fix these exact scaling issues. The property landed in Chrome 125 and is now in Chrome, Edge, and Safari 18.2+. Older Chromium versions and Firefox won’t recognize it yet. The transitions will still work, they’ll just use the default fade animation instead of your custom timing. Not the worst fallback.

You can also assign multiple classes to a single element, just like regular CSS classes. Something like view-transition-class: card featured is valid, and you can target it with either ::view-transition-group(*.card) or ::view-transition-group(*.featured). Handy when you want most products to transition the same way but need a few to stand out with a different animation style.

Don’t Name Everything Upfront

Everything so far has had view-transition-name sitting right there in the HTML or CSS from the moment the page loads. That works. But it has a cost that’s not obvious until you hit real-world scale.

Look at the CSS for both pages. Zero view-transition-name declarations. None. Every card in the grid is anonymous until the exact moment the user clicks one.

Here’s why that matters. When you put view-transition-name on an element in your stylesheet — just sitting there in CSS, assigned from page load — you’re telling the browser, “This element participates in every transition that happens on this page.” Every single navigation. The browser has to snapshot it, calculate its position, and set up the pseudo-element tree for it. For one hero image, who cares? For a grid of 48 product cards, that’s 48 elements being individually captured, diffed, and animated when the user only clicked one of them. The other 47 snapshots are pure waste.

On a fast machine you might not notice. On a mid-range Android phone loading a grid of product images over LTE? You’ll feel it. The transition stutters or the browser just skips it entirely because it can’t set everything up fast enough.

The fix is to treat view-transition-name like a just-in-time thing. Assign it at the moment of interaction, not at page load.

The lifecycle goes like this:

  1. User clicks a card on the listing page.
  2. Browser starts navigating — pageswap fires on the old page.
  3. Your pageswap handler looks at event.activation.entry.url to figure out where the user is going, finds the clicked card, slaps view-transition-name: product-42 on it.
  4. Browser snapshots that one named element (plus the default root transition).
  5. Navigation happens, new page loads.
  6. pagereveal fires on the incoming page.
  7. Your pagereveal handler reads the URL, finds the hero element, assigns the matching view-transition-name: product-42.
  8. Browser sees matching names on old and new snapshots — morphs between them.
  9. Transition finishes, your .finished promise resolves, you clear the names.

That’s it. One element named, one element transitioned, zero waste.

The event.activation object is your best friend here. On the outgoing page, event.activation.entry.url tells you where the navigation is headed. On the incoming page, you just read window.location. Between the two, you have everything you need to figure out which element to name without any global state, no sessionStorage tricks, no query parameter gymnastics beyond what your app already uses.

And about that cleanup step, removing the name after .finished resolves? It’s not just tidiness. If the user navigates back to the listing page and clicks a different card, you don’t want the old card still carrying a name from the previous transition. Stale names cause duplicate-name conflicts (instant transition death) or wrong-element matching (the new page morphs from the wrong card). Clean up after yourself.

This pattern is basically what Astro’s transition:name directive does under the hood. Same with Nuxt’s view transition support. They dynamically assign and remove names around the navigation lifecycle. The frameworks just hide the pageswap/pagereveal wiring behind a component attribute. You’re doing the same thing, just without the abstraction layer. Fewer moving parts, same result.

Practical Patterns for Real Content

The product grid example covers the most common case, but let’s run through a couple of other patterns you’ll hit in the wild.

Photo Galleries with Mixed Aspect Ratios

Galleries are tricky because every thumbnail might have a different aspect ratio, and the full-size view definitely will. The taffy fix from the Part 1 article is essential here, but you also want the transition to feel intentional rather than chaotic.

/* Gallery items get their own class for targeted animation */
::view-transition-group(*.gallery-item) {
  animation-duration: 0.5s;
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}

::view-transition-old(*.gallery-item),
::view-transition-new(*.gallery-item) {
  object-fit: cover;
  overflow: hidden;
}

/* Lightbox-style overlay - fade the background separately */
::view-transition-group(*.lightbox-bg) {
  animation-duration: 0.3s;
}

The trick with galleries is assigning the view-transition-name to the <img> itself rather than the surrounding card or container. You want the browser to morph the image from thumbnail size to lightbox size, not the card’s background, padding, and caption along with it. Name the image. Style the card. Keep them separate.

For the lightbox background (that dark overlay), give it its own view-transition-name and view-transition-class. It’ll fade in independently while the image morphs. Two transitions running in parallel, each with their own timing. Looks polished, and it’s just two names.

Tab or Section Transitions Within a Page

Not everything is a grid-to-detail pattern. Sometimes you’re transitioning between sections on the same page, e.g., dashboard tabs, multi-step forms, content panels. Same-document view transitions work great here, and the view-transition-class approach scales the same way.

/* Shared header that persists across tabs */
::view-transition-group(*.persistent) {
  animation-duration: 0s; /* don't animate - it should feel anchored */
}

/* Tab content that swaps */
::view-transition-group(*.tab-content) {
  animation-duration: 0.25s;
}

::view-transition-old(*.tab-content) {
  animation: slide-out-left 0.25s ease-in;
}

::view-transition-new(*.tab-content) {
  animation: slide-in-right 0.25s ease-out;
}

@keyframes slide-out-left {
  to {
    transform: translateX(-100%);
    opacity: 0;
  }
}

@keyframes slide-in-right {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
}

The animation-duration: 0s on persistent elements is worth calling out. If your site header has a view-transition-name (so it stays in place instead of participating in the default root cross-fade), you probably don’t want it animating at all. Zero-duration makes it snap to its new position instantly, which feels like it never moved. That’s the point — stable landmarks make the transitioning content feel grounded.

Dynamic Content and Infinite Scroll

Here’s a pattern that catches people off guard. You’ve got a product grid with infinite scroll, loading new items as the user scrolls down. Each new batch arrives via fetch() and gets appended to the DOM. Do those new items need view-transition-name?

No. Not until someone clicks one.

With the just-in-time pattern, it doesn’t matter whether an element existed at page load or was added dynamically five minutes later. The pageswap handler queries the DOM at the moment of navigation. If the element is there, it finds it, names it, done. Your infinite scroll items work identically to your initial page load items without any extra setup.

The one thing to watch out for: make sure your data-id attributes (or whatever you’re using to match elements) are unique across all loaded batches. If your API returns items with IDs and you’re using those for the view-transition-name, you’re already fine. If you’re generating IDs client-side, make sure they don’t collide when new batches load.

Don’t Make People Sick

/* The responsible way to set up view transitions */
@view-transition {
  navigation: auto;
}

/* All your animation customizations go INSIDE this media query */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-group(*.card) {
    animation-duration: 0.35s;
    animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  }

  ::view-transition-old(*.card),
  ::view-transition-new(*.card) {
    object-fit: cover;
  }

  /* Custom keyframes, staggered delays, the fun stuff - all in here */
  ::view-transition-old(root) {
    animation: fade-out 0.2s ease-in;
  }

  ::view-transition-new(root) {
    animation: fade-in 0.3s ease-out;
  }
}

/* If the user HAS requested reduced motion: instant cut, no animation */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0s !important;
  }
}

@keyframes fade-out {
  to {
    opacity: 0;
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

This isn’t a nice-to-have. I need to be blunt about that.

People with vestibular disorders — and there are a lot more of them than most developers realize — can get physically nauseous from unexpected motion on screen. Not “mildly annoyed.” Nauseous. Dizzy. Migraines that last hours. The prefers-reduced-motion media query exists because real people checked a box in their OS settings that says “please stop making me sick.” Ignoring it is the accessibility equivalent of removing a wheelchair ramp because stairs look cleaner.

The @view-transition opt-in can stay outside the media query. That’s fine, it just tells the browser, “I want cross-document transitions enabled.” The browser will still do an instant cut between pages, which is visually identical to a normal navigation. It’s the animation customizations that need to be gated: the durations, the easing curves, the custom keyframes. Wrap all of that in prefers-reduced-motion: no-preference and you’re covered.

That prefers-reduced-motion: reduce block at the bottom is a belt-and-suspenders thing. Even if you miss wrapping some animation rule, forcing animation-duration: 0s on all the transition pseudo-elements ensures nothing actually moves. The !important is ugly but justified here. you genuinely want this to override everything, no exceptions.

You already saw the conditional opt-in pattern back in Part 1:

/* You can also just disable transitions entirely for reduced-motion users */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

Either approach works. Wrapping the whole @view-transition rule means the browser won’t even attempt the transition – it’s a normal navigation, full stop. Keeping @view-transition active but killing the animation durations means the transition technically fires but completes instantly, which can matter if you have pagereveal logic that depends on event.viewTransition existing. Pick whichever fits your setup. Just don’t ship animated transitions without checking.

A thing worth considering here: “reduced motion” doesn’t necessarily mean “no motion.” Some users with vestibular sensitivities are fine with fades but not with sliding or zooming. You could offer a gentler alternative instead of killing all animation entirely.

@media (prefers-reduced-motion: reduce) {
  /* Instead of zero duration, use a quick crossfade only */
  ::view-transition-group(*) {
    animation-duration: 0.15s !important;
    animation-timing-function: linear !important;
  }

  ::view-transition-old(*) {
    animation: fade-out 0.15s linear !important;
  }

  ::view-transition-new(*) {
    animation: fade-in 0.15s linear !important;
  }
}

This is a judgment call. A fast, subtle cross-fade is less likely to trigger symptoms than a 400ms morphing animation with easing curves. But the safest option is always zero motion, and if you’re not sure, go with animation-duration: 0s. You can always add a gentler alternative later once you’ve tested it with actual users who rely on the setting.

Handle Old Browsers (By Doing Basically Nothing)

/* Feature detection, if you need it */
@supports (view-transition-name: none) {
  .card {
    /* maybe you want contain: paint for better snapshotting */
    contain: paint;
  }
}
// JS-side feature detection
if (document.startViewTransition) {
  // same-document transition API exists
}

// For cross-document transitions, there's no direct JS check -
// the browser either supports @view-transition in CSS or ignores it.
// That's... actually fine.

Here’s the thing though: you probably don’t even need that @supports check.

View transitions are progressive enhancement in the purest sense of the term. If a browser doesn’t understand @view-transition { navigation: auto; }, it ignores the rule. That’s how CSS works. The user clicks a link, the browser navigates normally, the new page loads. No animation, no morphing, no cross-fade. Just a regular page load. Which is exactly what every website on the internet did for the first 25 years of the web. It’s fine.

Nothing breaks. No JavaScript errors. No layout shifts. No fallback code to write. The view-transition-name properties get ignored. The ::view-transition-* pseudo-element selectors match nothing. Your pageswap and pagereveal event listeners either don’t fire or event.viewTransition is null and your guard clause returns early. The whole feature is designed to be invisible when it’s absent.

That’s the beauty of this API, honestly. It’s one of the rare web platform features where you don’t have to write a single line of fallback code. Firefox doesn’t support it yet? Fine — Firefox users get normal navigation. Safari’s working on it but hasn’t shipped? Cool, Safari users click links and pages load. Nobody gets an error. Nobody gets a broken layout. Nobody loses anything. They just don’t get the fancy animation, and most of them will never notice it was supposed to be there.

Worth noting where things actually stand today: Chrome and Edge have full support for cross-document view transitions, including view-transition-class. Safari also ships full cross-document support as of Safari 18.2. The momentum is clearly toward universal support, even though Firefox still holds it behind a flag for now.

The only time @supports matters is if you’re adding styles that only make sense in the context of view transitions — like contain: paint on elements to improve snapshot quality, or hiding some loading state that the transition would normally cover. Gate those behind @supports (view-transition-name: none) so non-supporting browsers don’t get the side effects without the payoff.

Failure is invisible. That’s the whole point.

Ship It

Look, I’ve been building websites for a long time, and there’s always been this unspoken trade-off: you want smooth, app-like transitions, you adopt a framework and a client-side router and a build step and a hydration strategy and suddenly you’re maintaining a small aircraft carrier just so a card can animate into a hero image.

That trade-off is dissolving.

Cross-document view transitions let an <a href> feel like a native app navigation. Two HTML files. Some CSS. Maybe a little JavaScript for the fancy stuff. The browser does the rest. That’s not a small thing – it changes which projects need a framework and which ones just assumed they did.

The spec is young. It’s Chromium-only right now. The rough edges are real – you’ve seen them across both parts of this series. But the API is designed so well that when it’s not supported, nothing breaks. Your site just works the way sites have always worked. And when it is supported, it feels like magic that came free.

Here’s a quick cheat sheet to take with you:

  • Opt in with CSS, not the deprecated meta tag: @view-transition { navigation: auto; }.
  • Both pages must opt in or no transition happens.
  • 4-second timeout starts at navigation, not at render – use pagereveal to catch TimeoutError.
  • Images stretch during transitions because pseudo-elements default to object-fit: fill – fix it with object-fit: cover on ::view-transition-old and ::view-transition-new.
  • view-transition-name = identity (unique per page), view-transition-class = styling hook (shared across elements).
  • Don’t name elements upfront – use pageswap and pagereveal to assign names just-in-time. But keep your pageswap logic fast — the browser gives you a narrow window (10-50ms) before snapshots.
  • Clean up names after viewTransition.finished resolves to avoid stale conflicts.
  • Gate animations behind prefers-reduced-motion: no-preference — this is not optional.
  • Progressive enhancement is built in — unsupported browsers just get normal page loads

The best animations are the ones you don’t have to maintain a framework to get.

Cross-Document View Transitions Series

  1. The Gotchas Nobody Mentions
  2. Scaling View Transitions Across Hundreds of Elements (You are here!)

Cross-Document View Transitions: Scaling Across Hundreds of Elements originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.

Read the whole story
alvinashcraft
50 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

GitHub for Beginners: Getting started with Git and GitHub in VS Code

1 Share

Welcome back to GitHub for Beginners. We’ve covered a lot this season, so make sure to check out our other episodes. Our most recent one was all about open source, what it is and how to contribute to the community.

This time, we’re going to take a look at VS Code, a free popular source code editor provided by Microsoft. It has a fair amount of functionality built in that integrates with GitHub, which is what we’ll be taking a look at today. Using GitHub in VS Code reduces context switching, streamlines your workflow, and boosts your productivity. By the end of this post, you’ll understand how to use VS Code to initialize a repository, switch branches, as well as stage, commit, and push your changes. And the best part is, you’ll be able to do all this without leaving the editor.

Note that if you want to follow along with this blog post (or the video), you will need to install both Git and VS Code. If you need a refresher on how to install Git, you can check out one of our earlier GitHub for Beginners episodes.

As always, if you prefer to watch the video or want to reference it, we have all of our GitHub for Beginners episodes available on YouTube.

First some basics

You probably already know that GitHub is a resource that hosts only copies of your code in repositories. So what is Git? Git is the program for managing that source code, and it can be used in multiple different ways (e.g., from the command line, through VS Code, etc.). Visual Studio Code, often abbreviated as VS Code, leverages Git to enable you to manage your code in GitHub.

Initializing a folder

The first step to using Git with VS Code is initializing a folder to reflect your repository on GitHub.

  1. Open VS Code.
  2. Select the top icon (the Explorer icon) in the left-hand column. It looks like two files on top of each other.
  3. Click the Open Folder button.
  4. In the file explorer, select a folder that has some code you want to upload to GitHub, and click Open.
  5. After opening your code, select the Source Control icon. By default, it’s the third icon from the top.
  6. Click the Initialize Repository button.

At this point, a few things will change in your UI. First, you can see the branch name in the bottom bar on the left-hand side. The default is main. You can rename your branch by using the Command Palette.

  1. To open the Command Palette, press Shift-Command-P on Mac or Ctrl-Shift-P on PC.
  2. In the Command Palette, start typing “rename” and select the Git: Rename Branch command.
  3. In the box, provide the new name of the branch and press Enter.

At this point, you can see that the name of the branch in the bottom-left corner has been updated to the new name. You can rename it back to main by following the same steps.

Another change you’ll see after initializing your repository is that each of the files in the Source Control Panel have a “U” next to them. “U” stands for untracked, meaning that these files are new or changed, but have not been added to the repository. To track a file, you just need to click the plus sign adjacent to the file name. If you want to track all of the files, you can click the top plus next to the word CHANGES.

When you do this, the file(s) that you select will be staged, and the letter next to them will change to “A”. This means the file is staged, but not yet uploaded to GitHub. In order to upload the changes, you’ll need to commit the files.

  1. Enter a message in the text box at the top of the Source Control window describing the commit. Alternatively, you could click the Copilot icon in the text box to have Copilot generate a commit message for you.
  2. Select the Commit button underneath the text box to commit your changes.

It’s that simple!

Creating and changing branches

Right now, you’re likely on the main branch. Remember that you can check the branch by looking in the bottom-left corner of your window. If this were a major app and you were adding new code or features, you’d want to create a new branch and use that for your work.

  1. Open the Command Palette by pressing Shift-Command-P on Mac or Ctrl-Shift-P on PC.
  2. Enter “create branch” in the text box.
  3. Select Git: Create Branch… from the list of options.
  4. Enter the name of the new branch in the box. For example, “new-features”.
  5. Press Enter.

Once you do this, VS Code will create the new branch and automatically transfer you to this branch. You can verify this by looking at the branch name in the bottom-left corner.

Tracking changes you make

Now that you’re in your working branch, go ahead and enter a line of code in a file. When you do this, you’ll notice that a thin green line appears on the right side of your editor next to the code you added. This section of the editor is called the gutter, and this green line reflects a new line of code that you added.

Move to a different line and make some changes in the line of code that already exists. When you do this, you’ll see a blue line with a pattern across it in the gutter. This line indicates that you’ve made changes to an existing line of code.

Finally, move to an unchanged line in the file and delete it. Notice that the gutter adds a red arrow. This indicates that a line of code was removed from the file.

When you modify this file, you can see that the file appeared in your Source Control window under the CHANGES header. If you hover over the filename, you’ll see several buttons appear. There are buttons to open a file, discard your changes, and stage your changes. Similar to before, if you have multiple files with changes, you can hover over the CHANGES header and see buttons that will let you review unstaged changes, open the changes, discard all the changes, and stage all the changes.

Viewing diffs

Sometimes you want to see the changes that you made in a file. VS Code lets you do this by performing side-by-side diffs without needing another tool. To see the changes on a file, click on the name of the file you want to see in the Source Control window. From here you can see the changes in the file and compare the differences.

Depending on your preferences, you can also view your diffs in what is called an inline view.

  1. Click the three dots () in the top-right of the diff view.
  2. Select Inline View from the drop-down menu.

This lets you see all of the changes in a single window without splitting it up over two separate views. From this view, you can even make edits inside of the diff view.

Once you’ve made any changes you want to make to the file, it’s time to upload them to GitHub. Following the steps we went over before, go ahead and stage your changes, and then commit your staged changes. Once you finish these steps, you shouldn’t have any files displayed in the Source Control window.

Merging branches

Note that changes you’ve uploaded will still be in your working branch. If you navigate back to the main branch, you’ll see the original code before the changes you made.

  1. Click the branch name in the bottom-left of the window.
  2. Branches names appear in the drop-down at the top of the program. Select the main branch.

In order to get these changes into your main branch, you’ll need to merge branches.

  1. In the Source Code window, click the three dots () next to CHANGES.
  2. In the context menu, hover over Branch and select Merge…
  3. The box at the top will prompt you with branches that you want to merge from. Select the branch with the changes you want to merge into main.

Congratulations! Now your main branch has incorporated the changes!

Publishing to GitHub

Let’s say you want to take your project and publish it up to GitHub. To do so, click the Publish Branch button in the Source Control window. VS Code will prompt you with whether you want to publish it as a private or as a public repository. Select the option you want, and then VS Code handles the rest.

Once VS Code finishes the publishing process, it will notify you that the project has been published to a repository on GitHub. You can click the Open on GitHub button to visit your project on GitHub and see it online.

Cloning a repository

Now let’s say you want to clone a repository so that you can work on it on your machine. This creates a local copy that you can use and sync the changes between the two locations. There are multiple ways that you can clone a repository, and this is an easy way to do it in VS Code.

  1. Navigate to the home page of the repository you want to clone.
  2. Click the green <> Code button at the top of the repository file list.
  3. In the drop-down menu, select the Copy URL to clipboard button next to the box containing the repository’s URL.
  4. Open VS Code.
  5. Open the Command Palette by pressing Shift-Command-P on Mac or Ctrl-Shift-P on PC.
  6. Type in “clone”.
  7. Select Git: Clone from the list of options.
  8. Paste the URL into the box.
  9. Select Clone from URL with the URL you pasted after it.
  10. A pop-up window will ask you for a location. Choose the folder where you want to store the project files.
  11. Click the Select as Repository Destination button.
  12. A pop-up manu will appear asking if you want to open the repository. Select Open.

Congratulations! You’ve just cloned the repository to your machine and can start to work on it in your local environment!

Model Context Protocol

Did you know that you don’t have to do everything manually in VS Code? You could leverage Model Context Protocol (MCP) to safely let AI tools access external tools and data. The first step is to add the GitHub MCP extension.

  1. In the left navigation bar, click the Extensions icon.
  2. In the search box, enter “@mcp github”.
  3. Select the GitHub extension from the list.
  4. In the description for the extension, click Install.
  5. A pop-up appears, asking you to allow the MCP server to authenticate. Select Allow.
  6. Select your GitHub username from the list.

At this point, the GitHub MCP server is installed. You can verify this by looking at the bottom of the Extensions view and seeing the section for installed MCP servers. With the MCP server installed, you can use Copilot chat to create some code for you, and it will do so by leveraging external tools where necessary.

  1. Open the chat window by selecting the Chat icon next to the Command Palette window.
  2. Enter a prompt asking Copilot to add some features to your project.

For an example of how this works, check out the video version of this episode which walks through asking Copilot to create several issues to improve a flash card application.

Next steps

And that’s it! We covered some of the most common ways developers use VS Code to interact with Git. We’ve gone over everything from creating repositories, to publishing on GitHub, and even threw in a little bit of using AI at the end. There are more advanced tips, but these elements are what you’ll use most frequently.

Happy coding!

The post GitHub for Beginners: Getting started with Git and GitHub in VS Code appeared first on The GitHub Blog.

Read the whole story
alvinashcraft
50 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

How to use Git and GitHub in VS Code | Tutorial for beginners

1 Share
From: GitHub
Duration: 9:04
Views: 232

Want to speed up your development process? Today, we are teaching you how to manage your version control without switching between windows. This beginner tutorial covers the essentials of using Git and GitHub right inside VS Code. We will guide you through initializing a new repo, branching, staging, and pushing your commits securely to GitHub. Grab your editor and code along with us to build your skills.

#VSCode #Git #GitHub

— CHAPTERS —

00:00 - Introduction to using Git and GitHub in VS Code
01:04 - Initialize, stage, and commit local changes
02:48 - Create new branches and merge code
05:08 - Publish to and clone from GitHub
06:23 - Use AI tools and the Model Context Protocol
08:21 - Summary and conclusion

Stay up-to-date on all things GitHub by connecting with us:

YouTube: https://gh.io/subgithub
Blog: https://github.blog
X: https://twitter.com/github
LinkedIn: https://linkedin.com/company/github
Insider newsletter: https://resources.github.com/newsletter/
Instagram: https://www.instagram.com/github
TikTok: https://www.tiktok.com/@github

About GitHub
It’s where over 180 million developers create, share, and ship the best code possible. It’s a place for anyone, from anywhere, to build anything—it’s where the world builds software. https://github.com

Read the whole story
alvinashcraft
51 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

EP279 Native Cloud Security: Is 'Good Enough' Actually Winning?

1 Share

Guests:

 Topics: 

  • In Episode 186, we debated 'Native vs. Third-Party' as a binary choice. Native seems to be a third-party vendor whose entire existence depends on the belief that cloud-native controls are superior. Does your platform validate the 'Cloud Provider' side of the debate (that their controls are enough), or does the fact that you exist prove the 'Third-Party' side (that native interfaces aren't enough)?
  • A key argument against native controls is an AWS WAF and a Google Cloud Armor don't behave the same way. If your tool manages native controls across multi-cloud, how do you handle the 'lowest common denominator' problem? Do you dumb down the policy to fit all clouds, or do you expose the unique complexity of each one?
  • GuardDuty and SCC produce similar but meaningfully different results. How do you abstract across that so an analyst or IR team isn't having to dig into the exact meaning of the different JSON fields in their output?
  • We often say native tools are 'good enough' for 80% of use cases but lack the depth of specialized third-party vendors (like a dedicated CNAPP or DLP). By betting your company on orchestrating native controls, are you effectively betting that 'good enough' is the future of the market? What happens when a customer needs a feature that the CSP hasn't built yet?
  • What fraction of your users are taking this from a "I'm 80% this one cloud, I need great coverage there and good enough elsewhere" vs "I'm truly multi-cloud" or even scarier "I have a workload that is active spanning clouds"? 
  • Do your customers push you towards helping with the kinds of SaaS platforms that SSPM vendors cover?
  • If AWS and Google Cloud suddenly decided to make their native security UIs perfect and unified tomorrow, would your company cease to exist? Or is the complexity of the cloud strictly increasing, guaranteeing you job security forever?

Related:





Download audio: https://traffic.libsyn.com/secure/cloudsecuritypodcast/EP279_not273_CloudSecPodcast.mp3?dest-id=2641814
Read the whole story
alvinashcraft
51 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Compile C#+XAML to a Native Windows, macOS, or Linux App, From Your Browser

1 Share
Featured image of post Compile C#+XAML to a Native Windows, macOS, or Linux App, From Your Browser

What if you could turn a C#+XAML project edited in your browser into a native Windows, macOS, or Linux desktop app, without leaving the browser, and without your code ever touching a server?

That’s what XAML.io v0.7 can now do. Click Publish, pick a platform, and a self-contained .exe, .app bundle, or Linux executable lands in your Downloads folder in well under a minute on a modern laptop. Everything runs in your browser tab: the compilation, the packaging, and even the Apple Mach-O ad-hoc code-signing pass for macOS bundles.

Preview status. This feature is in Preview / Alpha in XAML.io v0.7 (May 2026). Expect rough edges, occasional breakages, and changes between releases. We’d rather ship the rough version and iterate with you than wait for everything to be perfect. Bug reports and “this is missing X” notes are both welcome.

The Publish menu open in XAML.io, showing the three OS buttons under “Download as a desktop app”.

New to XAML.io? XAML.io is a free, browser-based IDE for building .NET applications using C# and XAML. It includes a visual drag-and-drop XAML designer with 100+ controls, a code editor, and in-browser .NET compilation via WebAssembly. No installation, no signup required. It’s built by Userware and powered by the open-source OpenSilver framework. Try it →


How it works

In XAML.io’s title bar, the Publish dropdown now has a “Download as a desktop app” section with three buttons inside it: one each for Windows, macOS, and Linux. Click a button and a dialog walks through what you’re about to get (format, size, first-launch caveats), then the publish pipeline runs locally in your browser tab. The browser’s native download dialog handles the rest.

There’s no signup. There’s no backend compile queue. The first time you publish for a given OS, the browser fetches a small per-platform launcher template (~30 MB, cached after first publish), assembles it with your compiled assemblies, and hands the result off as a download.

What you get on each platform

Windows

A self-contained .exe packaged in a .zip alongside an app/ folder (your compiled assemblies, sidecar DLLs, and resources) and a README.txt. Recipients extract the archive and double-click the .exe. No .NET install required on the recipient’s machine.

Windows SmartScreen will show a “Windows protected your PC” dialog on first launch because the .exe is unsigned (we haven’t yet shipped support for user-supplied code-signing certificates). The bundled README.txt has the “More info → Run anyway” bypass steps.

The Windows .exe download dialog inside XAML.io.

macOS

A self-contained .app bundle inside a .zip. The bundle is ad-hoc signed client-side as part of the publish pipeline (more on how that works below). Because there’s no Apple Developer ID involved, macOS Gatekeeper still prompts on first launch, but the bundled README.txt has the right-click → Open path for macOS 12-14 (Monterey, Ventura, Sonoma) and the System Settings → Privacy & Security → Open Anyway path for macOS 15 (Sequoia).

Apple Silicon (arm64) only for now. Intel Mac support is on the roadmap.

The macOS .app download dialog inside XAML.io.

Linux

A self-contained ELF executable in a .tar.gz. Recipients extract the archive and run the executable. The UI is rendered via WebKitGTK 4.1, which ships preinstalled on every current LTS or rolling release (Ubuntu 22.04+, Debian 12+, Fedora 38+, Mint 21+, Arch). x86_64 only for now.

The Linux executable download dialog inside XAML.io.

These are real native desktop apps

Same project, three OSes, three native processes:

The published app running natively on Windows.

The published app running natively on macOS.

The published app running natively on Linux.

The downloaded artifact is a real native .NET process, not a packaged web page or a WebAssembly container. Your C# runs as native code on the recipient’s machine via the .NET 10 runtime (JIT-compiled, in-process, with full access to threading, sockets, file I/O, and P/Invoke), the same execution model as a WPF or WinForms app.

The rendering model is different: XAML.io builds OpenSilver apps. At build time, OpenSilver’s XAML compiler emits C# that knows how to construct the visual tree. At runtime, that C# runs, and each OpenSilver control attaches its underlying DOM element to the document: a TextBox becomes an actual <textarea>, an Image becomes an <img>, a Path becomes an <svg> (see the v0.6 deep dive for the long version). In the published desktop app that DOM lives inside the OS’s built-in WebView (Edge WebView2 on Windows, WKWebView on macOS, WebKitGTK on Linux), embedded into the .NET process via Photino and driven by the OpenSilver runtime directly (no Blazor WASM in the desktop app; that’s IDE-only). So: native .NET execution, DOM-based UI rendering, no bundled browser engine, no WASM at runtime.


Reactions so far

“Fucking hell this is really getting so much better, thanks a whole lot to the team!!”
Infinite_Track_9210 on Reddit (source)


Under the Hood

There are four pieces worth pulling apart, because each one is the kind of thing that’s often assumed to need a backend.

1. The packaging pipeline runs entirely in your browser tab

No backend compile step. No server upload. No build queue. When you click Publish, the browser:

  1. Compiles your C#+XAML project to .NET assemblies using the same in-browser Roslyn that powers XAML.io’s Run button (see our earlier post on in-browser .NET compilation).
  2. Fetches a per-OS launcher template from XAML.io’s CDN. The template is a pre-built Photino app for that platform, bundled with the .NET runtime. It’s cached after first publish, so subsequent publishes for the same OS skip this step.
  3. Assembles your compiled assemblies + a small manifest.json + resources into the launcher’s expected sidecar layout, on an in-memory filesystem (Emscripten’s MEMFS, the same virtual FS Blazor WebAssembly uses).
  4. Re-packs the result into the right archive format (.zip for Windows and macOS, .tar.gz for Linux) with the correct executable bits, then hands the byte array to the browser’s native download dialog.

Your code never leaves your machine. The only network traffic during a publish is the one-time launcher-template fetch, which is the same pre-built bytes for everyone; the launcher knows nothing about your project.

2. Native .NET execution, DOM-based rendering

This is the part that surprises people, so it’s worth being explicit about: the WebAssembly part is only the IDE itself. The desktop apps it produces are real native .NET 10 processes. Your C# is JIT-compiled by the runtime and runs in-process, with the same execution model as a WPF or WinForms app.

The UI is rendered as real DOM elements (OpenSilver’s design choice; see the v0.6 deep dive), displayed inside the OS’s built-in WebView via Photino. In the published desktop app the OpenSilver runtime is loaded directly by Photino through the OpenSilver.Photino bridge: there’s no Blazor WebAssembly intermediate at runtime, and no client-side compilation either (that’s all IDE-only).

That separation matters in two practical ways:

  • No bundled browser engine. Photino uses the OS’s built-in WebView (Edge WebView2 / WKWebView / WebKitGTK) rather than bundling Chromium the way Electron does. That keeps the download size to roughly the cost of the .NET runtime alone.
  • C# / .NET libraries work the same as on any desktop .NET process. All the things WebAssembly normally sandboxes away (file I/O, sockets, threading, P/Invoke, native interop) work normally in the published app, because it’s a normal .NET process. Your code can do things the IDE itself can’t.

3. Even macOS ad-hoc code-signing happens client-side

This was the trickiest part to get working. Apple’s Mach-O format and the CodeDirectory hashing dance that produces a valid .app bundle signature are normally handled by codesign, which is a macOS-only command-line tool. To make publishing a .app from the browser work without a server-side signer, and without requiring you to be on a Mac, we needed an in-WebAssembly implementation of the signing pass.

The answer: we pulled the Mach-O writer and CodeDirectory hasher out of Filip Navara’s CodeSign / Melanzana, trimmed them to the parts we actually use, and run the ad-hoc signing pass entirely in the browser’s WebAssembly runtime. The bundle’s Mach-O binary is re-hashed, the _CodeSignature/CodeResources plist is regenerated, and the LC_CODE_SIGNATURE load command is rewritten. All of that happens on MEMFS, in C# compiled to WASM. The output is a valid ad-hoc signed bundle that Gatekeeper will accept (with the standard first-launch prompt that ad-hoc bundles always trigger).

The Melanzana subset is lazy-loaded: it ships as a separate ~500 KB compressed assembly that’s only fetched the first time you click Download as a macOS app. Windows-only and Linux-only publishers never download those bytes, so the IDE’s first-paint payload is unchanged for them.

Honestly, signing a macOS app from inside a browser tab is the kind of thing we’d have called impossible. Until we got it working.

4. File sizes

The launcher is a .NET 10 self-contained, single-file deployment with compression enabled (EnableCompressionInSingleFile=true) and trimming deliberately off (PublishTrimmed=false). Trimming is off on purpose: the launcher loads your compiled assemblies via Assembly.Load(byte[]) and the trimmer cannot reason about that. Compression brings an untrimmed self-contained .NET 10 app under 40 MB; without it, the same artifact lands closer to 60-80 MB.

Platform Compressed download What’s inside
Windows ~30-40 MB .zip Single-file .exe with the .NET 10 runtime + OpenSilver runtime + Photino bridge bundled inside, alongside an app/ folder with your compiled assemblies and a README.txt.
macOS ~30-40 MB .zip Ad-hoc-signed .app bundle following the same layout as Windows. Apple Silicon (arm64).
Linux ~30-40 MB .tar.gz Single-file ELF executable with the same internal layout. x86_64.

Sizes don’t balloon to Electron’s ~150 MB+ territory because Photino uses the OS’s built-in WebView rather than bundling Chromium. You pay for the .NET runtime, not for a browser engine.

The floor stays flat across platforms because the .NET runtime is the dominant cost. Project assets (images, fonts, media, embedded resources) add to that linearly.


Limitations and what’s missing

We want to be upfront about what isn’t there yet:

  • Preview / Alpha in XAML.io v0.7 (May 2026). Behavior, UI, and the on-disk layout of the produced artifacts may change between releases. We’d rather ship the rough version and iterate with feedback than wait for everything to be perfect.
  • No publisher-identity signing or notarization. Apps are unsigned in the Apple Developer ID / Windows Authenticode sense, so first-launch Gatekeeper and SmartScreen warnings still apply. Support for user-supplied code-signing certificates is on the way; for macOS, apps distributed via the web also need Apple’s notarization step for the smoothest first-launch experience, and we’ll wire that up alongside cert support. Until then, the bundled README.txt walks recipients through the one-click bypass for each OS.
  • Architecture coverage is partial. macOS publish is Apple Silicon (arm64) only. Linux publish is x86_64 only. Intel Macs and Linux ARM64 are on the roadmap.
  • Linux distro floor. Modern distros (Ubuntu 22.04+, Debian 12+, Fedora 38+, Arch, Mint 21+) ship WebKitGTK 4.1 preinstalled and run the published app with no setup. On older releases (Ubuntu 18.04 / 20.04, Debian 11) recipients may need a one-time apt install libwebkit2gtk-4.1-0 or equivalent. The bundled README.txt has the per-distro commands.
  • Publish is best on desktop browsers. The packaging pass uses the WebAssembly heap heavily; mobile browsers may run out of memory on larger projects. Editing and running on mobile is fine, as before.

What’s coming next

Feature Description
User-supplied code-signing certificates Upload your Apple Developer ID or Authenticode certificate; published apps then ship with publisher-identity signing and skip the first-launch warning entirely.
Broader CPU coverage macOS Intel (x86_64) and Linux ARM64 publish targets.
One-click web deployment Already on the v0.6 roadmap; the desktop publish flow is the local-distribution side of the same story.

Suggest or vote on features at feedback.xaml.io, ask questions on the forums, or contact us directly.


Try it

Open xaml.io, build something, click Publish, pick a platform. In well under a minute on a modern laptop you’ll have a real native desktop app you can hand to anyone with that OS.

xaml.io | Free. No install. No signup required.

Read the whole story
alvinashcraft
51 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories