Prompts Have Dependencies Too

You find a prompt on a forum thread. Four hundred upvotes. Someone says it transformed their code review workflow. You copy it, paste it into your setup, run it — and get results that are… fine. Not bad. Not what they described. You tweak some words. You add context. Still mediocre. You move on, quietly concluding that the hype was overblown. But the prompt wasn’t broken. You just ran an npm install that compiles fine and breaks at runtime. The dependency manifest was never written down.

The dependency nobody declares

Every prompt encodes a set of assumptions the author never articulated, because to them, those assumptions were invisible. They were part of the air they breathed while writing it. In code, we’ve learned — sometimes painfully — to declare our dependencies. You know what Python version is expected. You know what environment variables need to exist. You can run pip list and see the full picture. You can’t do any of that with a prompt. What you’re actually inheriting when you copy someone’s prompt is a crystallized set of decisions made in a specific context. And that context has at least three layers.

Layer 1: Context assumptions

The author wrote the prompt against the backdrop of their specific situation — their tech stack, their team’s conventions, their workflow. These shape the prompt in ways that are almost impossible to see from the outside. Take a prompt for “write a ticket.” That phrase carries a world of assumptions: what a ticket means in their process, how granular it should be, who reads it, what fields exist in their tracker. A developer working in a tight-knit four-person startup and a developer working inside an enterprise JIRA workflow might both call the output “a ticket” while meaning entirely different things. The prompt is optimized for one of those worlds. When you run it in the other, it’s not wrong — it just has the wrong priors.

Layer 2: Model assumptions

Prompts are also optimized for a specific model’s behavior, including its failure modes. A lot of the instruction in high-quality prompts isn’t telling the model what to do — it’s compensating for what the model tends to do wrong. “Don’t include preamble” exists because a particular model loved preamble. “Think step by step before answering” was a meaningful intervention at a specific point in model development. Some of those instructions still load-bear; others are now inert, or actively counterproductive on a different model. The model the author used shaped the prompt as much as their intentions did. When you switch models — even to a newer version of the same one — you’re running code compiled against a different runtime. Some prompts survive the transition. Others quietly degrade.

Layer 3: Author assumptions

This one is the most overlooked: the author’s own writing style is baked into the prompt. Language models infer intent from phrasing, formality, vocabulary, and structure. A prompt written by someone who writes terse, technical prose signals something different than the same instructions written by someone who writes in full paragraphs with hedges and qualifications. The model picks up on those signals and calibrates accordingly. When you copy a prompt written in someone else’s voice, you’re running it in yours — and those two things interact in ways that are hard to predict. The gap between what the author got and what you get can be almost entirely stylistic, with nothing explicitly wrong.

The problem compounds in agent systems For individual prompts, these hidden dependencies are an annoyance. For agent systems, they’re an architectural hazard. A complex agent setup — skills, sub-agents, orchestration logic — is a stack of prompts, each carrying its own embedded assumptions, all interacting with each other. When you borrow someone’s agent setup, you’re not copying a function. You’re cloning a codebase, minus the README, minus the architecture docs, minus the incident history that shaped the current design. Each component was tuned against the others. The routing logic assumes certain output formats from upstream agents. The summarization step was calibrated to the verbosity of a specific model. Pull one piece out and drop it into a different system, and what breaks may not be obvious or immediate. This is why “it worked in their demo” is not a strong signal that it’ll work in your setup. The demo is a controlled environment. Your system is a different runtime.

Read prompts like code

The prescription isn’t “don’t share prompts.” Sharing is valuable. The prescription is to read shared prompts critically — the same way you’d read someone else’s code before integrating it. Look for load-bearing phrases. When you see an unusual instruction, ask: what is this compensating for? What problem was the author solving? If you removed this sentence, what would degrade? Treat shared prompts as illustrations of principles, not as portable solutions. A prompt that handles ambiguous user input in a specific way is showing you an approach. Extract the approach. Rewrite the implementation in your context, against your model, in your voice. The prompt is the output of a reasoning process. The reasoning is what you actually want.

What good sharing looks like The missing artifact in most prompt sharing is the README. Not a heavy document — just the few sentences that declare the dependencies. What model was this built for? What does the input look like? What conventions does it assume? What did you try that didn’t work? Some communities are starting to develop norms around this. But for the most part, prompts get shared the way code was shared before package managers: as raw files, trust the consumer to figure out the rest. A simple norm shift would help: when you share a prompt that worked well, add a short context block. “Built for Claude Sonnet, internal code review workflow, assumes tickets are already written.” Ten seconds of annotation that saves the next person an hour of debugging.

The mental model shift

The underlying reframe is this: prompts are not reusable artifacts. They are crystallized decisions, made in a specific context, by a specific person, against a specific model, for a specific purpose. That doesn’t make sharing useless — it makes understanding essential. The goal when you encounter a well-crafted prompt isn’t to copy it. It’s to understand the decisions well enough to make your own. When you do that, you stop wondering why the four-hundred-upvote prompt underdelivered in your setup. You start asking the right question instead: what problem was this person solving, and how would I solve mine?

I’ve spent the last two weeks trying spec-driven development, and my own prompts beat every “AI dev kit” I’ve tried. Those kits mostly waste my time, tokens, and context. #SpecDrivenDevelopment #LLM #DevTools #PromptEngineering #AI

“Review fatigue” is real. AI can flood teams with generated PRs, making it easier for subtle bugs to slip through. Without better review strategies, we risk trading speed for reliability. #AIcoding #DevPatterns #CodeReview

A hidden cost of React Native: your app is a patchwork of packages by different authors. Even if each works fine alone, they’ve likely never been tested together. Native dev has other challenges—but this kind of fragility isn’t one of them. #ReactNative #MobileDevelopment

First I saw a wave of posts about a0.dev and now bolt.new added #reactnative support. There’s definitely been a lot of progress in #ai lately and it might convince some companies to switch from fully native to RN to reduce costs.

I’ve been using Orion browser on iOS for about a week now. Ability to install Firefox and Chrome extensions is nice, but I found that don’t really need any new extensions. My current set of Safari extensions on iOS cover all my needs.

React Native for SwiftUI devs

After a quick review of TypeScript handbook I started reading React Native documentation. I hope my notes will be useful for other SwiftUI devs who want to try RN.

@ViewBulilder -> JSX

JSX is an embeddable XML-like syntax that allows developers to describe UI in a declarative manner, just like view builders in SwiftUI. TypeScript files that contain JSX must have .tsx extension.

To “escape back” into TypeScript world use curly braces.

export default function Hello() {
    const name = "John";
    return (
      <Text>Hello, {name}!</Text>
    );
}

You can only use curly braces in two ways inside JSX: 1 As text directly inside a JSX tag, as in the example above. 2 As attributes immediately following the = sign: src={avatar} will read the avatar variable, but src="{avatar}" will pass the string "{avatar}".

In addition to primitive types and simple expressions, you can pass objects in JSX. Objects are also denoted with curly braces, like { name: "John Johnson", posts: 5 }. So, to pass a TS object into JSX, you must wrap the object in another pair of curly braces: person={ { name: "John Johnson", posts: 5 }}. You will see this with inline CSS styles in JSX.

Conditional rendering

  1. You can use if to assign conditional content to a variable and later use it inside JSX block:
if (isLoggedIn) {
  content = <AdminPanel />;
} else {
  content = <LoginForm />;
}
return (
  <Container>
    {content}
  </Container>
);
  1. Or, you can use the conditional ? operator.
<Container>
  {isLoggedIn ? (
    <AdminPanel />
  ) : (
    <LoginForm />
  )}
</Container>

You can’t use if inside JSX tag like you do in SwiftUI’s ViewBuilder.

Responding to Events

You can respond to events by declaring event handler functions inside your components:

function MyButton() {
  function handleTap() {
    alert('You tapped me!');
  }

  return (
    <Button onPress={handleTap}>
      Tap me
    </Button>
  );
}

Notice how onClick={handleTap} has no parentheses at the end! We do not want call the event handler function, only to pass it down, otherwise function would be called on each re-render. If you want to define your event handler inline, wrap it in an anonymous function like so: <MyButton onPress={() => alert('You tapped me!')}>

@State

To add state to your component import useState from React, and then declare a state variable inside your component:

import { useState } from 'react';
function MyButton() {
  const [count, setCount] = useState(0); 
  // …
}

You’ll get two things from useState: the current state (count), and the function that lets you update it (setCount). While you can choose any names, the conventional format is [variable, setVariable]. Initially, count will be 0 since you initialized it with useState(0). Just like with @State this state is tied to a component instance, so if you declare multiple instances of the same component each of them will have its own state. Keep multiple state variables if their states are unrelated, but if two variables frequently change together, consider merging them into a single object. To update the state, simply call the setter function: function handleClick() {setCount(count + 1);}.

You can’t use regular variables to represent state for two reasons: 1. Local variables don’t persist between renders. When React renders this component a second time, it renders it from scratch - it doesn’t consider any changes to the local variables. 2. Changes to local variables won’t trigger renders. React doesn’t realise it needs to render the component again with the new data.

A Quick Word About Hooks

Functions starting with use (like useState above) are called Hooks. useState is a built-in Hook provided by React. You can find other built-in Hooks in the API reference. You can also write your own Hooks by combining the existing ones. Comparing to other functions hooks have several restrictions. Hooks can only be called at the top level of your components or your own Hooks. You can’t call Hooks inside conditions, loops, or other nested functions. Hooks are functions, but it’s helpful to think of them as unconditional declarations. You “use” React features at the top of your component similar to how you “import” modules at the top of your file. If you want to use useState in a condition or a loop, extract a new component and put it there.

@Binding

In React Native, the closest equivalent to SwiftUI’s @Binding is passing props and using callback functions, but it’s not as seamless as SwiftUI’s two-way binding: <ChildComponent count={count} onPress={handlePress} />

The information you pass down like this is called props. In the child component read props and use them to implement child functionality:

function ChildComponent({ count, onPress }) {
  return (
    <Button 
        title={`${count}`} 
        onPress={() => {
          // Must use the passed setter function
          onCountChange(prevCount => prevCount + 1)
      }}
    />
  );
}

Props can have default values: function Avatar({ user, size = 100 }).

For more complex scenarios, you might use:

  • Props drilling (passing props through multiple layers)
  • Context API (similar to SwiftUI’s @Environment, see example below)
  • Dedicated state management libraries

@Observable / @ObservableObject -> useReducer

useReducer is very different from observation mechnisms in SwiftUI and probably closer to TCA, but this is probably the closest thing React has to offer. Instead of updating properties of an object that describes state of the app you will need to write a function that take current state and an “action” that says how state should be updated. This function called reducer and there is a lot of information available on this topic, so I won’t go in to details.

The following example should be enough for general understanding of how it works.

function counterReducer(state, action) {
    switch (action.type) {
        case 'INCREMENT':
            return { count: state.count + 1 };
        case 'DECREMENT':
            return { count: state.count - 1 };
        default:
            return state;
    }
}

function CounterComponent() {
    const [state, dispatch] = useReducer(counterReducer, { count: 0 });
    return (
        <View>
            <Text>{state.count}</Text>
            <Button title="Increment" onPress={() => dispatch({ type: 'INCREMENT' })} />
        </View>
    );
}

Official documentation has a very detailed explanation with usage examples.

.onAppear & .onChange -> useEffect

Syntax and implementation differ, but the concept is similar: declaratively handle lifecycle and state-dependent operations without mixing them directly into the rendering logic.

function UserProfile() {
  const [userId, setUserId] = useState(null);
  const [userData, setUserData] = useState(null);

  // Similar to .onAppear - runs when component first mounts
  useEffect(() => {
    // Initial data fetch when component appears
    fetchUserProfile(userId);
  }, []); // Empty dependency array means "run only on mount"

  // Similar to .onChange - runs when userId changes
  useEffect(() => {
    if (userId) {
      // Fetch user data whenever userId changes
      fetchUserProfile(userId);
    }
  }, [userId]); // Dependency array specifies which values trigger the effect

  return (
    <View>
      <Text>{userData?.name}</Text>
    </View>
  );
}

Warning: The main purpose of Effects is to allow components connect to and synchronize with external systems. Don’t use Effects to orchestrate the data flow of your application. See you might not need an Effect for details.

Other considerations

Design your components as pure functions that don’t cause side effects. In React, side effects usually belong inside event handlers. Event handlers are functions that React runs when you perform some action—for example, when you click a button. Even though event handlers are defined inside your component, they don’t run during rendering! So event handlers don’t need to be pure. In React there are three kinds of inputs that you can read while rendering: props, state, and context. You should always treat these inputs as read-only.

I took extensive notes while reading the React Native and React documentation this weekind. I plan to post them after verifying that all code snippets in my notes are correct. Similar to my previous posts, I will try to explain React Native using concepts familiar to SwiftUI developers.

I found a few additional differences in the TypeScript documentation that are not mentioned in the Swift-TypeScript cheatsheet I linked in one of my previous posts:

  • The return type of a function can be inferred. If there’s no return type specified in the declaration, it doesn’t mean the function returns void.
  • Optionality in TypeScript is implemented at the property level, not in the type system. To make a property optional, add ? at the end of the property name. There are utility types in TypeScript that can change the optionality of properties, such as Partial<Type> and Required<Type>.
  • Use extends keyword to add type constraints in generics: <T1, T2 extends T1>
  • Use & to “combine” two or more types into one. It will create a new type that contains properties from all combined types.
  • Use | to create a union type that can be one of the types being “united”.
  • To make illegal states unrepresentable Swift devs often use enums with associated types. To mimic Swift’s switch on enum with associated types use union of interfaces representing associated types and add a tag with unique literal types to each interface. Then, use switch on the tag of union type, that’s enough to guarantee type safety.
  • For RawRepresentable enums it’s usually more efficient to use a union of literal types representing values because it doesn’t add extra run-time code like TS’s enum. Another alternative is const enum, but there is something in the TS documentation about potential problems with it.
  • keyof is a way to provide a type for dynamic member lookups. It’s somewhat similar to keypath in Swift.
  • typeof, when used inside conditions, allows for narrowing the type in a conditional branch.
  • “Indexed access type” allows to get type of a property by using indexed access syntax: type ContactID = Contact["id"]
  • Tuple in TS is a sort of Array type that knows exactly how many elements it contains, and exactly which types it contains at specific positions. Example: type StringNumberPair = [string, number]; describes array whose 0 index contains a string and whose 1 index contains a number.

During my journey from Swift to TypeScript, I often wonder how developers migrating in the opposite direction feel. What do they appreciate about Swift, and what aspects of TypeScript do they miss?

One of the things I like about Swift is the ability to make illegal states unrepresentable using type system. It looks like something similar is possible in TypeScript using combination of union types and literal types.

So far, Swift-Typescript Cheatsheet dabbott.github.io/webdev-pr… has been the best resource for learning TS for me.

I’ve been writing native iOS apps using Swift for the last 8 years (and years of ObjC before that). Next week I will start working on a new project with ReactNative and TypeScript. All learning materials I found are written either for web developers or people completely new to programming 🤔