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

Asus just announced the OLED Xbox Ally X of my dreams

1 Share
The Xbox Ally X20, and the glasses you’ll have to buy with it. | Image: Asus

If you asked me what I'd change about the Xbox Ally X handheld - aside from fixing Windows, I mean - I'd tell you two key things.

First, give me a bigger, better screen. Even a little bit bigger, so games feel less claustrophobic and with less ugly bezel. Second, get rid of the "Library" button. I am so tired of an accidental press booting me out of my game and into the Xbox library without a simple way to get back.

With the just-announced ROG Xbox Ally X20, Asus did both - and then some. It's now a slick translucent handheld with drift-resistant GuliKit TMR joysticks, a transforming D-pad that goes from 8-way to 4-way by dropping its cor …

Read the full story at The Verge.

Read the whole story
alvinashcraft
2 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Type-safe tool definitions come to the Copilot .NET SDK

1 Share

As a big fan of the GitHub Copilot SDK, I'm trying to keep up with the list of changes. The beta 6 version of the GitHub Copilot SDK now ships a typed CopilotTool.DefineTool helper that lets you configure override flags, skip-permission behavior, and other Copilot-specific metadata without sprinkling magic strings throughout your codebase. This makes working with Copilot tools in C# a lot cleaner.

It brings the .NET SDK in line with typed helper APIs already available in other language SDKs and makes tool definitions easier to write, refactor, and reason about.

The old way vs the new way

Before this change, wiring up Copilot-specific metadata meant relying on loosely typed configuration — easy to mistype, hard to discover, and brittle to refactor. Now, the same intent is expressed through a clean, strongly typed API:

Before

// Metadata via magic strings
options["is_override"] = "true";
options["skip_permission"] = "true";

After

new CopilotToolOptions
{
  OverridesBuiltInTool= true
}

Usage

Here's what defining a tool with override behavior looks like end-to-end:

var tool = CopilotTool.DefineTool(
    "edit",
    new CopilotToolOptions { OverridesBuiltInTool = true },
async (params, ctx) => { // your custom edit implementation });

What OverridesBuiltInTool means: marking a tool as an override tells Copilot to use your implementation in place of the built-in behavior for that tool name. Combined with SkipPermission, you can also bypass the standard user-confirmation step for trusted automation workflows.

A real world example

In my PowerPilot application, I originally created my own Tool function to tackle the same issue:

// All our tools are safe read-only lookups — skip the permission prompt.
var skipPermission = new ReadOnlyDictionary<string, object?>(
    new Dictionary<string, object?> { ["skip_permission"] = true });

AIFunction Tool(Delegate fn, string name, string description) =>
    AIFunctionFactory.Create(fn, new AIFunctionFactoryOptions
    {
        Name = name,
        Description = description,
        AdditionalProperties = skipPermission,
    });

return new List<AIFunction>
{
    Tool(() => energyPlugin.GetCurrentPower(),
        "get_current_power",
        "Get the current real-time power consumption and production in kilowatts"),

    Tool(() => energyPlugin.GetTodayStatsAsync(),
        "get_today_stats",
        "Get energy consumption and production statistics for today"),

    Tool(([Description("Time period: today, yesterday, week, or month")] string period = "today")
            => energyPlugin.GetEnergyStatsAsync(period),
        "get_energy_stats",
        "Get energy statistics for a given time period"),

    Tool(() => energyPlugin.GetHourlyProfileAsync(),
        "get_hourly_profile",
        "Get the hourly energy profile for today to understand usage patterns"),

    Tool(([Description("Appliance name, e.g. dishwasher, washing machine, dryer, EV charger")] string appliance)
            => energyPlugin.GetApplianceAdviceAsync(appliance),
        "get_appliance_advice",
        "Get advice on the best time to run a high-power appliance based on current and historical production data"),

    Tool(() => weatherPlugin.GetCurrentWeatherAsync(),
        "get_current_weather",
        "Get current weather including temperature, cloud cover, and estimated solar irradiance"),

    Tool(() => weatherPlugin.GetSolarForecastAsync(),
        "get_solar_forecast",
        "Get the solar production forecast for the next 24 hours based on weather data"),
}.AsReadOnly();

But thanks to the updated code I no longer need this and can update my code to the following:

// All our tools are safe read-only lookups — skip the permission prompt.
var toolOptions = new CopilotToolOptions
{
    SkipPermission = true,
};

return new List<AIFunction>
{
    CopilotTool.DefineTool(()=> energyPlugin.GetCurrentPower(),
        toolOptions,new AIFunctionFactoryOptions() 
        { 
            Name = "get_current_power", 
            Description= "Get the current real-time power consumption and production in kilowatts" 
        }),
    CopilotTool.DefineTool(()=> energyPlugin.GetTodayStatsAsync(),toolOptions, new AIFunctionFactoryOptions()
        {
            Name = "get_today_stats",
            Description= "Get energy consumption and production statistics for today"
        }),
    CopilotTool.DefineTool(([Description("Time period: today, yesterday, week, or month")] string period = "today")
        => energyPlugin.GetEnergyStatsAsync(period),toolOptions, new AIFunctionFactoryOptions()
        {
            Name = "get_energy_stats",
            Description= "Get energy statistics for a given time period"
        }),
    CopilotTool.DefineTool(() =>energyPlugin.GetHourlyProfileAsync(), toolOptions, new AIFunctionFactoryOptions()
        {
            Name = "get_hourly_profile",
            Description= "Get the hourly energy profile for today to understand usage patterns"
        }),
    CopilotTool.DefineTool(([Description("Appliance name, e.g. dishwasher, washing machine, dryer, EV charger")] string appliance)
            => energyPlugin.GetApplianceAdviceAsync(appliance),toolOptions, new AIFunctionFactoryOptions()
            {
                Name = "get_appliance_advice",
                Description= "Get advice on the best time to run a high-power appliance based on current and historical production data"
            }),
    CopilotTool.DefineTool(()=> weatherPlugin.GetCurrentWeatherAsync(),toolOptions, new AIFunctionFactoryOptions()
            {
                Name = "get_weather_forecast",
                Description= "Get current weather including temperature, cloud cover, and estimated solar irradiance"
            }),
    CopilotTool.DefineTool(()=> weatherPlugin.GetSolarForecastAsync(),toolOptions, new AIFunctionFactoryOptions()
            {
                Name = "get_solar_forecast",
                Description= "Get the solar production forecast for the next 24 hours based on weather data" 
            })
}.AsReadOnly();

Conclusion

The Copilot SDK team has been steadily unifying the developer experience across language bindings. Typed tool definition helpers have existed in other SDKs for a while — this update closes the gap for C# developers who previously had to work against the grain to express the same intent.

The practical benefits are real: CopilotToolOptions is discoverable via IntelliSense, validated at compile time, and refactor-friendly. If a property is renamed in a future SDK version, your build tells you immediately — instead of a runtime surprise.

More information

Release v1.0.0-beta.6 · github/copilot-sdk

Read the whole story
alvinashcraft
2 hours ago
reply
Pennsylvania, USA
Share this story
Delete

What Is A Story Goal? The Secret To A Strong Plot

1 Share

What is a story goal in fiction writing? Learn how a concrete story goal helps writers create stronger plots, build conflict, and structure novels that keep readers engaged.

You have to keep your characters busy if you want to write a novel. You have to give them a crisis, a reason to overcome it, and a way out.

Once you have an appropriate inciting moment created by a worthy antagonist, you have to decide what your protagonist is going to do about it.

What Is A Story Goal? The Secret To A Strong Plot

Your protagonist has to set a concrete goal that they believe will solve the crisis. The protagonist’s goal is known as the story goal. This goal is the foundation of your plot.

Your story at its most basic:

Before you start writing, complete this:

My novel is a story about  ______________________  (protagonist’s name)

who wants to ____________________________________  (ultimate physical story goal).

Characters Must Have A Physical Story Goal

For the purpose of plotting, you should not choose an abstract goal. You must choose a tangible goal.

Characters always have abstract story goals. We are always on journeys of self-discovery where we worry about our feelings. This is a given, but never let these become more important than physical goals with deadlines.

We are also always our own worst enemies. Don’t choose ‘overcoming yourself’ as a goal. If you do choose an abstract story goal, your character will spend too much time alone, thinking and boring your readers.

Remember that love is not a story goal – it is an emotion. Read: Love Is Never The Goal – Even When You Write Romance

To recap from a previous post on story goals, wanting to find ‘love’ or ‘acceptance’ or ‘justice’ is not enough. When your character is loved, accepted or vindicated after achieving a physical goal, you have a story.

Your Character Needs One (Or More) Of These

To define a physical story goal a character needs:
  1. To get something physical, or
  2. To cause something physical, or
  3. To escape something physical, or
  4. To resolve something physical, or
  5. To survive something physical.

Examples:

  1. If you want to write about a woman who wants to ‘find herself’, make her travel somewhere.
  2. If your lawyer wants justice, give them a client whose life needs to be saved.
  3. If your lonely character wants love, give them a situation to deal with where they can find the love of their life.

Suggested reading, with examples of story goals: Do Your Characters Have SMART Story Goals?

The Last Word

I’ve explained what a story goal should be in this post. A strong story goal helps give your novel focus and direction. When your protagonist has a clear goal, it becomes easier to build conflict, raise the stakes, and keep readers turning the page. But, how do you know if your story goal is good enough to support your story? Find out in our next post: 5 Essential Criteria For Creating Successful Story Goals

Additional Reading: 12 Point Checklist For Your Story Goal

Amanda Patterson

© Amanda Patterson

If you liked this blogger’s writingyou may enjoy:

  1. 45 Ways To Avoid Using The Word ‘Very’
  2. What Is Tone? 155 Words To Describe An Author’s Tone
  3. What Is Direct & Indirect Characterisation? & Which One Should I Use?
  4. 20 Fun Ways To Find Plot Ideas For Your Story
  5. All About Pacing: 4 Key Questions Every Writer Should Ask
  6. Past Tense Or Present Tense: Which Works Best For Your Story?
  7. A Guide To The 17 Most Popular Fiction Genres
  8. How To Write A Spy Novel
  9. How To Outline A Short Story – For Beginners
  10. 6 Sub-Plots Every Writer Should Know

Top Tip: Sign up for our free daily writing links

The post What Is A Story Goal? The Secret To A Strong Plot appeared first on Writers Write.

Read the whole story
alvinashcraft
2 hours ago
reply
Pennsylvania, USA
Share this story
Delete

New Desalination System Turns Seawater Into Drinking Water and Useful Salts - Including Lithium

1 Share
"Scientists have developed a solar desalination system that turns seawater into drinking water without creating environmentally damaging brine," reports ScienceDaily. "Special laser-textured metal panels use sunlight to evaporate water while automatically moving salt deposits away from the working surface, preventing clogging. The process was successfully tested with water from three oceans and can recover nearly all salts as solids. Those leftover materials could even become a source of valuable lithium for batteries." (The research team was led by University of Rochest professor Chunlei Guo and published their results in the journal Light: Science & Applications.) The University of Rochester has made an announcement: The technology uses solar panels made of black metal etched with femtosecond lasers to make the surface super light-absorbing and superwicking — or extremely attractive to water. The panels have a laser-treated active region that pulls a thin layer of water across the surface, absorbs nearly all solar radiation, distills the water, and deposits the leftover salts and minerals into the panel's untreated sides or "passive" region so that the salt does not clog the active region and disrupt continuous desalination... Guo's team precisely etched the black metal's grooves so the various salts and minerals in ocean water would simply slough off... [I]t extracts nearly 100 percent of the salts in solid form. This could not only produce an abundant supply of table salt, but it could also be used to extract more precious minerals, including lithium, which is used in the lithium-ion batteries that power electric vehicles and other electronics. In a related paper in the Journal of Materials Chemistry A, Guo and his colleagues show how they can use the same superwicking solar panels to separate lithium from the rest of other salts in desalination. Embedding nanoparticles made of hydrogen titanate in the tiny grooves of the black metal surface isolates the lithium from other salts and minerals...Using water samples from Great Salt Lake, the researchers extracted about 50 percent of the lithium from the salts left behind by the desalination process. Guo says now that the superwicking desalination technology has been demonstrated in proofs of concept on small-scale devices, he sees the technology inherently scalable, capable of improving global access to drinking water and building more sustainable supply chains for precious minerals. "The National Science Foundation, the Bill & Melinda Gates Foundation, and Worldwide Universities Network supported this research."

Read more of this story at Slashdot.

Read the whole story
alvinashcraft
2 hours ago
reply
Pennsylvania, USA
Share this story
Delete

How To Fix Common TypeScript Issues With Qodana

1 Share
How to fix common TypeScript issues with Qodana

Most TypeScript projects already run ESLint with @typescript-eslint. That covers a lot: explicit any, floating promises, non-null assertions, and more. If your linting setup is solid, you’re catching the obvious issues in the editor before code review.

ESLint rules can’t produce cross-file findings. Each rule runs within a single file’s scope, which means ESLint can’t tell you that an export is unused everywhere in the codebase, that an any-typed value from one file is causing unsafe assumptions five files away, or that two components implement the same logic independently. That’s the gap Qodana fills.

Here are five TypeScript issues worth addressing, organized by what ESLint handles and where it runs out of scope.

Implicit any spreading through your codebase

ESLint’s no-explicit-any catches places where you write any. It doesn’t track what happens when any enters your codebase from external sources, such as from response.json(), a third-party library without types, or an untyped import. Once an externally-typed any value enters your code, it propagates silently through property accesses and function calls. ESLint’s no-unsafe-* rules can catch this, but only if you’re using @typescript-eslint/recommended-type-checked, which requires type-aware linting and is significantly less common than the standard recommended config.

async function getUser(id: string) {
  const res = await fetch(`/api/users/${id}`);
  const data = await res.json(); // type: any

  return data.profile.name; // no error — crashes if profile is undefined
}

response.json() returns any in the standard lib. Everything downstream is untyped. The compiler accepts any property name and any method call. The bug surfaces at runtime. Qodana tracks how any flows through the program across files. When an any-typed value reaches a code path where a specific shape is assumed, it flags the discrepancy, even if that’s several function calls away from where any entered the codebase.

Adding UserResponse doesn’t fix this. It just moves the lie closer to the crash. Type the boundary instead:

interface UserResponse {
  profile: { name: string };
}

const data: UserResponse = await res.json();

If the API response shape changes, the type error surfaces at compile time.

Non-null assertions used as shortcuts

ESLint’s no-non-null-assertion flags every ! operator uniformly. That works, but many teams disable the rule or add broad exceptions because legitimate uses, like after a runtime check, get flagged alongside the dangerous ones. The signal gets noisy, the rule gets turned off, and the problem disappears from view.

function renderUser(user: User | null) {
  return `Hello, ${user!.name}`; // crashes at runtime if user is null
}

const button = document.querySelector(".submit-btn");
button!.addEventListener("click", handleSubmit); // crashes if element doesn't exist

Both examples compile without errors. Both crash under predictable conditions. The ! is often added to silence a type error without fixing the underlying issue.

The correct approach is to handle the null case:

function renderUser(user: User | null) {
  if (!user) return "Guest";
  return `Hello, ${user.name}`;
}

const button = document.querySelector(".submit-btn");
if (button) {
  button.addEventListener("click", handleSubmit);
}

Qodana surfaces non-null assertions as a separate category in the report. Not every ! is wrong, but seeing them gathered in one place makes it easier to distinguish the legitimate uses from the shortcuts, without having to choose between a noisy rule and no rule at all.

Floating promises

ESLint’s @typescript-eslint/no-floating-promises is effective, but it’s a type-aware rule. It requires TypeScript type checking to be enabled in your ESLint config via parserOptions.project. In projects where that’s not configured, or configured only for part of the codebase, the rule silently doesn’t run on uncovered files.

async function onSubmit(data: FormData) {
  saveToDatabase(data); // Promise<void>, not awaited
  router.push("/success"); // runs before save completes
}

TypeScript accepts this without complaint. Calling an async function without await is considered valid syntax, and the return value is discarded. However, this behavior is incorrect: The user sees the success page before the save completes, and any database error is silently swallowed.

async function onSubmit(data: FormData) {
  await saveToDatabase(data);
  router.push("/success");
}

Qodana’s analysis is type-aware by default across the whole project, without requiring ESLint’s TypeScript integration to be separately configured. Floating promises get flagged consistently regardless of how the project’s ESLint setup is structured.

Unused exports

noUnusedLocals in tsconfig catches unused variables within a file. Exported symbols are excluded by design. From the compiler’s perspective, something outside the current file might import them. ESLint’s eslint-plugin-import provides an import/no-unused-modules rule that can detect this, but it requires scanning the entire dependency graph on every lint run and carries significant performance overhead on large codebases. For most projects, it’s not practical to keep it enabled.

// utils/format.ts
export function formatCurrency(n: number): string { ... }
export function formatPercent(n: number): string { ... } // removed feature, still here
export function formatBytes(n: number): string { ... }    // never imported anywhere

All three pass without a warning. But formatPercent and formatBytes are dead code. They add maintenance surface, slow down refactors, and mislead developers who assume exported symbols are in use.

Detecting this requires whole-project analysis. Qodana builds a reference graph across the entire codebase and tracks every import and re-export. Symbols that appear only as sources, never as import targets, get flagged. Neither tsc nor ESLint can do this.

Duplicated logic across files

ESLint doesn’t have native duplication detection. Standalone tools like jscpd exist for this, but they’re not part of your linting pipeline. That means separate setup, separate maintenance, and another thing to remember. The result: logic that gets copied between components or utility files accumulates without anyone flagging it.

// components/UserCard.tsx
function formatUserName(user: User): string {
  if (!user.firstName && !user.lastName) return "Anonymous";
  return [user.firstName, user.lastName].filter(Boolean).join(" ");
}
// components/UserBadge.tsx
function getDisplayName(user: User): string {
  if (!user.firstName && !user.lastName) return "Anonymous";
  return [user.firstName, user.lastName].filter(Boolean).join(" ");
}

This isn’t a style issue. It means bug fixes need to be applied in multiple places, and when they aren’t, behavior diverges silently between the two copies.

Qodana detects duplicated code across files as part of the same analysis pass that surfaces type issues and unused exports. When it appears in the report alongside everything else, it’s harder to deprioritize than a separate tool nobody remembers to run.

Setting up Qodana for your TypeScript project

All five issues above are visible in Qodana’s default profile for JavaScript and TypeScript projects. Here is a minimal qodana.yaml to get you started:

  version: "1.0"
  linter: jetbrains/qodana-js:2026.1                                                                                                                                                                                                             
  bootstrap: npm ci                                                                                                                                                                                                                              
  profile:                                                                                                                                                                                                                                       
    name: qodana.recommended                                                                                                                                                                                                                     
  failThreshold: 0                                                                                                                                                                                                                               
  exclude:        
    - name: All                                                                                                                                                                                                                                  
      paths:
        - dist                                                                                                                                                                                                                                   
        - node_modules

If the first run surfaces hundreds of existing issues, don’t let that block CI adoption. Qodana’s baseline feature captures the current state of the project in a qodana.sarif.json file. Commit it, and from that point on, CI only fails on newly introduced problems. The existing backlog stays visible in the report, but it doesn’t block every PR while you work through it.

Fix common TypeScript issues

Ready to fix common TypeScript issues with Qodana?

Try Qodana and let us know what you think.

Try Qodana Ultimate Plus

We’d like to extend a special thank-you to Qodana developer Lev Liadov for his contribution to this guide.

Read the whole story
alvinashcraft
2 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Judging beautiful docs, AI fatigue, and tool slop

1 Share
In this podcast, I chat with Fabrizio Ferri-Benedetti about what makes documentation beautiful — drawing on Italo Calvino's literary principles of lightness and quickness — the reality of AI review fatigue versus creator fatigue, why most vibe-coded tools are actually 'tool slop,' developing internal skills for repeatable doc processes, and the emerging future of running local AI models.

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