Im going back to writing code by hand


09 मई, 2026


यहाँ k10s है: https://github.com/shvbsle/k10s/tree/archive/go-v0.4.0

234 प्रतिबद्ध. ~30 सप्ताहांत. क्लाउड के साथ पूरी तरह से वाइब-कोडेड सत्रों पर निर्मित, जब भी मेरे टोकन कुछ भेजने के लिए पर्याप्त समय तक चलते थे।

मैं अपने टीयूआई टूल को संग्रहीत कर रहा हूं और इसे स्क्रैच से फिर से लिख रहा हूं।

k10s की शुरुआत एक GPU-जागरूक Kubernetes डैशबोर्ड के रूप में हुई (और AI के साथ कुछ गंभीर बनाने में मेरा पहला प्रयास)। k9s के बारे में सोचें, लेकिन इसे NVIDIA क्लस्टर चलाने वाले लोगों के लिए बनाया गया है, जो लोग वास्तव में GPU उपयोग, DCGM मेट्रिक्स और कौन से नोड्स बेकार बैठे हैं और $32/घंटा खर्च कर रहे हैं। मैंने इसे गो विद बबल टी में बनाया [1] और यह काम कर गया.

थोड़ी देर के लिए… 🙁

इन 7 महीनों में मैंने सीखा कि इसका मूल्य 1690 पंक्तियों से भी अधिक है model.go मैं फेंक रहा हूँ. और मुझे लगता है कि गंभीर वाइब-कोडिंग करने वाला कोई भी व्यक्ति इससे लाभान्वित हो सकता है, क्योंकि यह हिस्सा ज्यादा सतह पर नहीं आता है (मुझे लगता है कि यह डेमो रीलों के नीचे दब जाता है और वेग जीत जाता है)।

टीएल;डॉ: एआई फीचर्स लिखता है, आर्किटेक्चर नहीं। जितनी देर आप इसे बिना किसी बाधा के चलने देंगे, मलबा उतना ही खराब होता जाएगा। वेग आपको यह सोचने पर मजबूर कर देता है कि आप तब तक जीत रहे हैं जब तक सब कुछ एक साथ ढह नहीं जाता।

वाइब कोडिंग हाई

मैंने सितंबर 2025 के अंत में k10s शुरू किया। पहले कुछ सप्ताह जादुई थे। मैंने क्लाउड को “लाइव अपडेट के साथ एक पॉड्स व्यू जोड़ें” का संकेत दिया और तेजी से काम किया, यह काम कर गया। संसाधन सूची दृश्य, नेमस्पेस फ़िल्टरिंग, लॉग स्ट्रीमिंग, पैनल का वर्णन, कीबोर्ड नेविगेशन। प्रत्येक सुविधा साफ-सुथरी थी क्योंकि प्रोजेक्ट इतना छोटा था कि एआई पूरी चीज़ को संदर्भ में रख सकता था।

मूल k9s क्लोन में शायद 3 सप्ताहांत लगे। पॉड्स, नोड्स, परिनियोजन, सेवाओं के लिए संसाधन दृश्य। एक कमांड पैलेट. घड़ी-आधारित लाइव अपडेट। विम कीबाइंडिंग. सभी कार्य, सभी वाइब-कोड एक ही सत्र में। मैं शायद अपनी सामान्य गति से 10 गुना अधिक गति से निर्माण कर रहा था और यह अविश्वसनीय लग रहा था।

तब मुझे मुख्य विक्रय बिंदु चाहिए था।

K10s के मौजूद होने का पूरा कारण GPU फ़्लीट दृश्य है। एक समर्पित स्क्रीन जो आपको प्रत्येक नोड का जीपीयू आवंटन, डीसीजीएम से उपयोग, तापमान, पावर ड्रा, मेमोरी दिखाती है। में दफनाया नहीं गया kubectl describe node आउटपुट, लेकिन वहीं रंग-कोडित स्थिति के साथ एक उद्देश्य-निर्मित तालिका में। पीले रंग में निष्क्रिय नोड्स. हरे रंग में व्यस्त. लाल रंग में संतृप्त.

gpuview

नकली जीपीयू नोड्स पर बेड़ा दृश्य

और क्लाउड ने इसे एक बार में ही मार दिया। मैंने फ्लीट व्यू के लिए संकेत दिया, इसने उत्पन्न किया FleetView संरचना, टैब फ़िल्टरिंग (जीपीयू/सीपीयू/सभी), आवंटन बार के साथ कस्टम रेंडरिंग। यह सुंदर लग रहा था. मैं ऊंची सवारी कर रहा था.

फिर मैंने टाइप किया :rs pods पॉड्स दृश्य पर वापस जाने के लिए।

कुछ भी प्रस्तुत नहीं किया गया. मेज खाली थी. लाइव अपडेट बंद हो गए थे. मैंने नोड्स पर स्विच किया, इसने फ़्लीट व्यू के फ़िल्टर से पुराना डेटा दिखाया। मैं फ्लीट में वापस गया, टैब की गिनती गलत थी।

देव वस्तु ने स्वयं को भस्म कर लिया था।

यह ब्लॉग पोस्ट का शीर्षक है. यहीं पर मैंने पहली बार हस्तक्षेप किया। 7 महीनों से मैं बिना बैठे और वास्तव में क्लॉड द्वारा लिखे गए कोड को पढ़े बिना संकेत दे रहा था और शिपिंग कर रहा था। मैं अंतर को देखूंगा, इसे संकलित सत्यापित करूंगा, खुश पथ का परीक्षण करूंगा, आगे बढ़ूंगा। लेकिन अब कुछ बुनियादी तौर पर टूट चुका था और मैं उससे बाहर निकलने का रास्ता नहीं सुझा पा रहा था।

तो मैं बैठ गया और पढ़ा model.go. सभी 1690 पंक्तियाँ। मैं भयभीत हो गया था.

यह इस तरह दिखता था। उन सभी पर शासन करने के लिए एक संरचना:

type Model struct {
    // 3rd party UI components
    table        table.Model
    paginator    paginator.Model
    commandInput textinput.Model
    help         help.Model

    // cluster info and state
    k8sClient         *k8s.Client
    currentGVR        schema.GroupVersionResource
    resourceWatcher   watch.Interface
    resources         []k8s.OrderedResourceFields
    listOptions       metav1.ListOptions
    clusterInfo       *k8s.ClusterInfo
    logLines          []k8s.LogLine
    describeContent   string
    currentNamespace  string
    navigationHistory *NavigationHistory
    logView           *LogViewState
    describeView      *DescribeViewState
    viewMode          ViewMode
    viewWidth         int
    viewHeight        int
    err               error
    pluginRegistry    *plugins.Registry
    helpModal         *HelpModal
    describeViewport  *DescribeViewport
    logViewport       *LogViewport
    logStreamCancel   func()
    logLinesChan      <-chan k8s.LogLine
    horizontalOffset  int
    mouse             *MouseHandler
    fleetView         *FleetView
    creationTimes     []time.Time
    allResources      []k8s.OrderedResourceFields  // fleet's unfiltered set
    allCreationTimes  []time.Time                  // fleet's timestamps
    rawObjects        []unstructured.Unstructured
    ageColumnIndex    int
    // ...
}

यूआई विजेट. K8s ग्राहक. लॉग, वर्णन, बेड़े के लिए प्रति-दृश्य स्थिति। नेविगेशन इतिहास. कैशिंग. माउस को संभालना. सभी एक संरचना में. और यह Update() विधि एक 500-लाइन फ़ंक्शन प्रेषण थी msg.(type) 110 स्विच/केस शाखाओं के साथ।

यही वह क्षण है जब मैंने वाइब-कोडिंग बंद कर दी और सोचना शुरू कर दिया।

logswork

ठीक है, मुझे लगता है कि मैं आपको अपने माउस से लॉग कॉपी करने दूँगा। क्या गलत जा सकता है?

मलबे से पांच टेंट

एआई को एक कोडबेस बनाते हुए देखने के 7 महीने बाद मैंने जो निष्कर्ष निकाला, वह यहां दिया गया है जो धीरे-धीरे खुद ही खत्म हो जाता है। इनमें से प्रत्येक कुछ ऐसा है जो मैंने गलत किया है, एआई-सहायता प्राप्त कोडिंग के साथ ऐसा क्यों होता है, और आपको वास्तव में अपने में क्या डालना चाहिए CLAUDE.md या agents.md इसे रोकने के लिए.


सिद्धांत 1: एआई सुविधाओं का निर्माण करता है, वास्तुकला का नहीं।

हर बार जब मैंने क्लाउड को किसी फीचर के लिए कहा, तो वह उपलब्ध हो गया। बिल्कुल सही. पहली कोशिश में फ्लीट व्यू ने काम किया। लॉग स्ट्रीमिंग ने काम किया. माउस सपोर्ट ने काम किया. समस्या यह है कि प्रत्येक सुविधा को समान स्थिति साझा करने वाली 49 अन्य सुविधाओं के बारे में किसी भी जागरूकता के बिना “अभी यह काम करें” के संदर्भ में कार्यान्वित किया गया था।

यहाँ क्या है resourcesLoadedMsg हैंडलर जैसा दिखता है. यह वह कोड है जो हर बार आपके द्वारा दृश्य स्विच करने पर चलता है:

case resourcesLoadedMsg:
    m.logLines = nil       // Clear log lines when loading resources
    m.horizontalOffset = 0 // Reset horizontal scroll on resource change

    if m.currentGVR != msg.gvr && m.resourceWatcher != nil {
        m.resourceWatcher.Stop()
        m.resourceWatcher = nil
    }
    m.currentGVR = msg.gvr
    m.currentNamespace = msg.namespace
    m.listOptions = msg.listOptions
    m.rawObjects = msg.rawObjects

    // For nodes: store the full unfiltered set, classify, then filter
    if msg.gvr.Resource == k8s.ResourceNodes && m.fleetView != nil {
        m.allResources = msg.resources
        m.allCreationTimes = msg.creationTimes
        if len(msg.rawObjects) > 0 {
            m.fleetView.ClassifyAndCount(m.rawObjectPtrs())
        }
        m.applyFleetFilter()
    } else {
        m.resources = msg.resources
        m.creationTimes = msg.creationTimes
        m.allResources = nil
        m.allCreationTimes = nil
    }

देखें if msg.gvr.Resource == k8s.ResourceNodes && m.fleetView != nil सशर्त? यह बेड़ा दृश्य सामान्य संसाधन लोडिंग पथ के अंदर विशेष-आवरण वाला है। कस्टम व्यवहार की आवश्यकता वाले प्रत्येक नए दृश्य को यहां एक और शाखा मिलती है। और प्रत्येक शाखा को फ़ील्ड के सही संयोजन को मैन्युअल रूप से साफ़ करने की आवश्यकता होती है अन्यथा पिछले दृश्य का डेटा ख़त्म हो जाएगा।

कितने = nil इस फ़ाइल में क्लीनअप लाइनें मौजूद हैं? मैंने गिना:

m.logLines = nil       // Clear log lines when loading resources
m.allResources = nil   // Clear fleet data when not on nodes
m.resources = nil      // Clear resources when loading logs
m.resources = nil      // Clear resources when loading describe view
m.logLines = nil       // Clear log lines when loading describe view
m.resources = nil      // Clear resources when loading yaml view
m.logLines = nil       // Clear log lines when loading yaml view
m.logLines = nil       // ... two more in other handlers
m.logLines = nil

1690-लाइन फ़ाइल में बिखरे हुए नौ मैनुअल शून्य असाइनमेंट। एक चूकें और आपको पिछले दृश्य से भूत डेटा प्राप्त होगा। ऐसा तब होता है जब कोई दृश्य अलगाव नहीं होता है। एआई इस पैटर्न को समय के साथ ख़राब होते नहीं देख सकता क्योंकि प्रत्येक प्रॉम्प्ट केवल एक कोड पथ को छूता है।

इसके बजाय क्या करें: किसी भी कोड से पहले आर्किटेक्चर स्वयं लिखें। कोई अस्पष्ट डिज़ाइन दस्तावेज़ नहीं. इंटरफ़ेस, संदेश प्रकार और स्वामित्व नियमों का एक ठोस सेट। फिर उन नियमों को अपने में रखें CLAUDE.md इसलिए AI उन्हें हर संकेत पर देखता है:

# Architecture Invariants (CLAUDE.md)

- Each view implements the View trait. Views do NOT access other views' state.
- All async data arrives via AppMsg variants. No direct field mutation from background tasks.
- Adding a new view MUST NOT require modifying existing views.
- The App struct is a thin router. It owns navigation and message dispatch. Nothing else.

यदि आप इन्हें लिखेंगे तो AI इनका अनुसरण करेगा। यह आपके लिए उनका आविष्कार नहीं करेगा।


सिद्धांत 2: ईश्वर वस्तु डिफ़ॉल्ट एआई आर्टिफैक्ट है।

एआई सिंगल-स्ट्रक्चर-होल्ड्स-एवरीथिंग की ओर आकर्षित होता है क्योंकि यह न्यूनतम समारोह के साथ तत्काल संकेत को संतुष्ट करता है। लेकिन यह बदतर हो जाता है. क्योंकि वहां कोई दृश्य पृथक्करण नहीं है, कुंजी संभालना एक दुःस्वप्न बन जाता है। यहां इसके लिए वास्तविक कुंजी प्रेषण है s चाबी:

case m.config.KeyBind.For(config.ActionToggleAutoScroll, key):
    if m.currentGVR.Resource == k8s.ResourceLogs {
        m.logView.Autoscroll = !m.logView.Autoscroll
        if m.logView.Autoscroll {
            m.table.GotoBottom()
        }
        return m, nil
    }
    // Shell exec for pods and containers views
    if m.currentGVR.Resource == k8s.ResourcePods {
        // ... 20 lines to look up selected pod, get name, namespace ...
        return m, m.commandWithPreflights(
            m.execIntoPod(selectedName, selectedNamespace),
            m.requireConnection,
        )
    }
    if m.currentGVR.Resource == k8s.ResourceContainers {
        // ... container exec logic ...
        return m, m.commandWithPreflights(m.execIntoContainer(), m.requireConnection)
    }
    return m, nil

एक कीबाइंडिंग. आप किस दृश्य में हैं, इसके आधार पर तीन पूरी तरह से भिन्न व्यवहार s कुंजी का अर्थ है लॉग में “ऑटोस्क्रॉल”, पॉड्स में “शेल”, और कंटेनर में “शेल इन कंटेनर”। यह सब एक ही फ्लैट में है switch क्योंकि कोई प्रति-दृश्य कुंजी मानचित्र नहीं हैं। एआई ने इसे उत्पन्न किया क्योंकि मैंने कहा था “पॉड के लिए शेल समर्थन जोड़ें” और इसने निकटतम कुंजी हैंडलर ढूंढ लिया और इसे जाम कर दिया।

और देखो कैसे Enter काम करता है. यह ड्रिल-डाउन हैंडलर है:

case m.config.KeyBind.For(config.ActionSubmit, key):
    // Special handling for contexts view
    if m.currentGVR.Resource == "contexts" {
        // ... 12 lines ...
        return m, m.executeCtxCommand([]string{contextName})
    }
    // Special handling for namespaces view
    if m.currentGVR.Resource == "namespaces" {
        // ... 12 lines ...
        return m, m.executeNsCommand([]string{namespaceName})
    }
    if m.currentGVR.Resource == k8s.ResourceLogs {
        return m, nil
    }
    // ... 25 more lines of generic drill-down ...

प्रत्येक दृश्य एक सपाट प्रेषण में एक सशर्त है। की 20+ घटनाएँ हैं m.currentGVR.Resource == इस एकल फ़ाइल में एक प्रकार विभेदक के रूप में उपयोग किया जाता है। प्रकार नहीं. स्ट्रिंग तुलना. प्रत्येक नए दृश्य का अर्थ है प्रत्येक हैंडलर को छूना।

इसके बजाय क्या करें: इसे अपने में डालो CLAUDE.md:

# State Ownership Rules

- NEVER add fields to the App/Model struct for view-specific state.
- Each view is a separate struct implementing the View trait/interface.
- Each view declares its own key bindings. The app dispatches keys to the active view.
- If you need to add a keybinding, add it to the relevant view's keymap, not a global one.
- Adding a view means adding a file. If your change requires modifying existing views, stop and ask.

एआई हमेशा सबसे छोटा रास्ता अपनाएगा (“एक और इफ-ब्रांच जोड़ें”)। आपका काम प्रत्येक आह्वान पर पढ़ी जाने वाली फ़ाइल में रेलिंग लगाकर सबसे छोटे पथ को भी सही पथ बनाना है।


सिद्धांत 3: वेग भ्रम आपका दायरा बढ़ाता है।

यह मनोवैज्ञानिक है, तकनीकी नहीं, और मुझे लगता है कि यह सबसे खतरनाक है।

जब मैंने k10s शुरू किया, तो मुझे एक GPU-केंद्रित टूल चाहिए था। प्रशिक्षण क्लस्टर चलाने वाले लोगों के लिए। एक विशिष्ट दर्शक वर्ग जिसका मैं हिस्सा हूं। लेकिन वाइब-कोडिंग ने हर चीज़ को सस्ता बना दिया। “ओह, मैं एक सत्र में पॉड्स व्यू जोड़ सकता हूं? मुझे तैनाती भी जोड़ने दीजिए। और सेवाएं। और एक पूर्ण कमांड पैलेट। और माउस समर्थन। और संदर्भ। और नेमस्पेस।”

resourceview

लानत है मैंने इसमें सब कुछ जोड़ दिया है…

अचानक मैं k9s का निर्माण कर रहा था। एक सामान्य प्रयोजन कुबेरनेट्स टीयूआई। सभी के लिए। क्योंकि AI ने ऐसा महसूस कराया कि प्रत्येक सुविधा निःशुल्क है।

यह मुफ़्त नहीं था. प्रत्येक विशेषता ईश्वर वस्तु में एक और शाखा थी। यहां कीबाइंडिंग संरचना है:

type keyMap struct {
    Up, Down, Left, Right    key.Binding
    GotoTop, GotoBottom      key.Binding
    AllNS, DefaultNS         key.Binding
    Enter, Back              key.Binding
    Command, Quit            key.Binding
    Fullscreen               key.Binding  // log view
    Autoscroll               key.Binding  // log view (also shell in pods!)
    ToggleTime               key.Binding  // log view
    WrapText                 key.Binding  // log + describe view
    CopyLogs                 key.Binding  // log view
    ToggleLineNums           key.Binding  // describe view
    Describe                 key.Binding  // resource views
    YamlView                 key.Binding  // resource views
    Edit                     key.Binding  // resource views
    Shell                    key.Binding  // pods (CONFLICTS with Autoscroll!)
    FilterLogs               key.Binding  // log view
    FleetTabNext             key.Binding  // fleet view only
    FleetTabPrev             key.Binding  // fleet view only
}

सभी दृश्यों के लिए एक फ्लैट कीमैप। माता-पिता में टिप्पणियाँ दर्शाती हैं कि प्रत्येक बाइंडिंग किस दृश्य पर लागू होती है। Autoscroll और Shell दोनों s. यह “काम करता है” क्योंकि प्रेषण जाँच करता है m.currentGVR.Resource अभिनय से पहले. लेकिन इसका मतलब यह है कि आप स्थानीय स्तर पर कीबाइंडिंग के बारे में तर्क नहीं कर सकते। एक कुंजी क्या करती है, यह जानने के लिए आपको संपूर्ण 500-लाइन अपडेट फ़ंक्शन का पता लगाना होगा।

जटिलता अदृश्य रूप से जमा हो रही थी जबकि वेग मीट्रिक ने कहा “आप शिपिंग कर रहे हैं!”

इसके बजाय क्या करें: एक विज़न दस्तावेज़ लिखें जो स्पष्ट रूप से बताता हो कि आप किसके लिए निर्माण नहीं कर रहे हैं, और अपने दायरे की सीमा निर्धारित करें CLAUDE.md:

# Scope (do NOT expand beyond this)

k10s is for GPU cluster operators. Not all Kubernetes users.
Supported views: fleet, node-detail, gpu-detail, workload. That's it.
Do NOT add generic resource views (pods, deployments, services).
Do NOT add features that duplicate k9s functionality.
If a feature request doesn't serve someone running GPU training jobs, reject it.

वाइब-कोडिंग आपको ऐसा महसूस कराती है जैसे आपके पास असीमित कार्यान्वयन बजट है। आप ऐसा नहीं करते. आपके पास अनंत LINE बजट है (AI उतना कोड उत्पन्न करेगा जितना आप चाहते हैं)। लेकिन आपके पास हमेशा की तरह समान सीमित जटिलता वाला बजट है। आर्किटेक्चर झुकने से पहले केवल इतनी सारी सुविधाओं का समर्थन कर सकता है, भले ही आपने उन्हें कितनी भी तेजी से लिखा हो। CLAUDE.md स्कोप अनुभाग क्या आप पहले से ना कह रहे हैं, इससे पहले कि उच्च वेग आपको हाँ कहने के लिए मना ले।


सिद्धांत 4: स्थितीय डेटा एक टाइम बम है।

K10s में प्रत्येक संसाधन कुबेरनेट्स एपीआई से प्राप्त किया गया था और तुरंत समतल किया गया था:

type OrderedResourceFields []string

कॉलम की पहचान पूरी तरह से स्थितीय थी। यहां बेड़े दृश्य के लिए सॉर्ट फ़ंक्शन है। इंडेक्स एक्सेस को देखें:

func sortFilteredResources(rows []k8s.OrderedResourceFields, times []time.Time, tab FleetTab) {
    sort.SliceStable(indices, func(a, b int) bool {
        ra := rows[indices[a]]
        rb := rows[indices[b]]

        switch tab {
        case FleetTabGPU:
            // Sort by Alloc column (index 3) ascending
            allocA, allocB := "", ""
            if len(ra) > 3 {
                allocA = ra[3]
            }
            if len(rb) > 3 {
                allocB = rb[3]
            }
            return allocA < allocB

        case FleetTabCPU:
            // Sort by Name column (index 0) ascending
            nameA, nameB := "", ""
            if len(ra) > 0 {
                nameA = ra[0]
            }
            if len(rb) > 0 {
                nameB = rb[0]
            }
            return nameA < nameB

        case FleetTabAll:
            // GPU nodes first, then CPU nodes.
            // Within GPU: sort by Alloc (index 3).
            // Within CPU: sort by Name (index 0).
            computeA, computeB := "", ""
            if len(ra) > 2 {
                computeA = ra[2]
            }
            if len(rb) > 2 {
                computeB = rb[2]
            }
            aIsGPU := strings.HasPrefix(computeA, "gpu")
            bIsGPU := strings.HasPrefix(computeB, "gpu")
            // ...
        }
    })
}

ra[3] एलोक है. ra[2] गणना है. ra[0] नाम है. ये जादुई संख्याएं हैं. इंडेक्स 3 को “एलोक” से जोड़ने वाली एकमात्र चीज़ एक टिप्पणी और इसमें परिभाषित कॉलम क्रम है resource.views.json:

{
  "nodes": {
    "fields": [
      { "name": "Name",     "weight": 0.28 },
      { "name": "Instance", "weight": 0.15 },
      { "name": "Compute",  "weight": 0.12 },
      { "name": "Alloc",    "weight": 0.12 },
      ...
    ]
  }
}

इंस्टेंस और कंप्यूट के बीच एक कॉलम जोड़ें? हर प्रकार, हर सशर्त प्रतिपादन, हर जगह जो कहती है ra[2] या ra[3] अब चुपचाप गलत है. संकलक आपकी सहायता नहीं कर सकता क्योंकि यह सब कुछ है []string. और JSON कॉन्फिगरेशन सॉर्ट व्यवहार, सशर्त प्रतिपादन, या कस्टम ड्रिल लक्ष्यों को व्यक्त नहीं कर सकता है, इसलिए वे गो कोड में रहते हैं जो JSON से स्थितीय मान्यताओं को हार्डकोड करते हैं।

एआई इस पैटर्न को उत्पन्न करता है क्योंकि यह “फ़ेच डेटा” से “रेंडर टेबल” तक का सबसे छोटा रास्ता है। ए []string किसी भी टेबल विजेट को तुरंत संतुष्ट करता है। टाइप की गई संरचनाओं के लिए पहले से अधिक समारोह की आवश्यकता होती है। तो एआई तेज़ रास्ता चुनता है, और छह महीने बाद आप डिबग कर रहे हैं कि सॉर्ट “आवंटन” कॉलम में “नाम” मान क्यों डालता है।

इसके बजाय क्या करें: इस निर्देश को अपने में रखें CLAUDE.md:

# Data Representation

- NEVER flatten structured data into []string, Vec, or positional arrays.
- All data flows as typed structs (FleetNode, PodInfo, etc.) until the render() call.
- Column identity comes from struct field names, not array indices.
- Sort functions operate on typed fields, never on positional access like row[3].
- The ONLY place strings are created for display is inside render()/view() functions.

तब आपकी टाइप की गई संरचना असंभव स्थितियों को असंभव बना देती है [2]:

struct FleetNode {
    name: String,
    instance_type: String,
    compute_class: ComputeClass,
    alloc: GpuAlloc,
}

जब कॉलम को फ़ील्ड नाम दिया गया हो तो आप गलत कॉलम के आधार पर सॉर्ट नहीं कर सकते। आप गलती से Alloc स्ट्रिंग्स की तुलना नामों के रूप में नहीं कर सकते। संकलक इसे आपके लिए लागू करता है। एआई हमेशा चुनेगा Vec क्योंकि यह शीघ्रता से संकेत को संतुष्ट करता है। आपका CLAUDE.md टाइप किए गए पथ को कम से कम प्रतिरोध का पथ बनाता है।


सिद्धांत 5: एआई के पास राज्य परिवर्तन का स्वामित्व नहीं है।

बबल टी वास्तुकला का एक सुंदर विचार है: Update() यह एकमात्र स्थान है जहां राज्य संदेशों द्वारा संचालित होता है। लेकिन k10s ने इसका उल्लंघन किया। updateTableMsg हैंडलर ने एक क्लोजर तैयार किया जिसने गोरौटाइन के अंदर से मॉडल फ़ील्ड को उत्परिवर्तित किया:

case updateTableMsg:
    return m, func() tea.Msg {
        // block on someone sending the update message.
        <-m.updateTableChan
        // Preserve cursor position across column/row updates so that
        // background refreshes don't reset the user's selection.
        savedCursor := max(m.table.Cursor(), 0)
        // run the necessary table view update calls.
        m.updateColumns(m.viewWidth)
        m.updateTableData()
        // Restore cursor, clamped to valid range.
        rowCount := len(m.table.Rows())
        if rowCount > 0 {
            if savedCursor >= rowCount {
                savedCursor = rowCount - 1
            }
            m.table.SetCursor(savedCursor)
        }
        return updateTableMsg{}
    }

यह लौटाया गया फ़ंक्शन (a tea.Cmd) बबल टी द्वारा एक अलग गोरोइन में निष्पादित किया जाता है। यह कॉल करता है m.updateColumns(m.viewWidth) और m.updateTableData() जो पढ़ते और लिखते हैं m.resources, m.table, m.viewWidth. इस दौरान, View() उसी फ़ील्ड को पढ़ते हुए मुख्य गोरोइन पर बुलाया जाता है। कोई ताला नहीं है. कोई म्यूटेक्स नहीं. द चैनल <-m.updateTableChan जब तक कोई अपडेट सिग्नल नहीं भेजता तब तक गोरोइन को ब्लॉक कर देता है, लेकिन कुछ भी नहीं रोकता है View() आधी-अधूरी अवस्था पढ़ने से.

यह एक पाठ्यपुस्तक डेटा रेस है। इसने 99% समय काम किया। 1% बार डिस्प्ले को इस तरह से खराब कर दिया कि मुझे लगा कि मैं पागल हो रहा हूँ।

एआई इसे उत्पन्न करता है क्योंकि “बस इसे क्लोजर में बदलें” कार्यशील कोड का सबसे छोटा रास्ता है। उचित संदेश भेजना (संदेश को वापस भेजें Update()होने देना Update() मुख्य लूप पर उत्परिवर्तन को परमाणु रूप से लागू करने के लिए अधिक प्रकार, अधिक प्लंबिंग की आवश्यकता होती है। एआई शीघ्रता के लिए अनुकूलन कर रहा है, समवर्ती के तहत शुद्धता के लिए नहीं।

इसके बजाय क्या करें: रेंडर-विज़िबल स्थिति में सभी उत्परिवर्तन मुख्य लूप पर होते हैं। अवधि। पृष्ठभूमि कार्यकर्ता डेटा तैयार करते हैं। वे इसे एक संदेश के रूप में भेजते हैं. मुख्य लूप संदेश प्राप्त करता है और उसे लागू करता है। यह एक नियम है जिसे आप समवर्ती यूआई कोड में नहीं तोड़ सकते।

// Background task:
tx.send(AppMsg::FleetData(nodes)).await;

// Main loop:
match msg {
    AppMsg::FleetData(nodes) => {
        self.fleet_view.update_nodes(nodes);
    }
}

कोई साझा परिवर्तनशील स्थिति नहीं. कोई डेटा दौड़ नहीं. नहीं “99% समय काम करता है।” इसे अपने में डालो CLAUDE.md:

# Concurrency Rules

- Background tasks (watchers, scrapers, API calls) NEVER mutate UI state directly.
- Background tasks send results through a channel as typed messages.
- Only the main event loop applies state mutations from received messages.
- render()/view() is a PURE function. No side effects. No I/O. No channel operations.
- If you need to update state from async work, define a new AppMsg variant.

यदि आपका AI डिफ़ॉल्ट रूप से यह पैटर्न उत्पन्न नहीं करता है, तो निर्देश इसे एकमात्र कानूनी विकल्प बनाता है।

मैं अब अलग तरीके से क्या कर रहा हूं

मैं रस्ट में k10s को फिर से लिख रहा हूँ। इसलिए नहीं कि रस्ट बेहतर है, बल्कि इसलिए कि यह वह भाषा है जिसे मैं चला सकता हूँ। मैंने इतना कुछ लिखा है कि कुछ गलत होने पर मैं इसका कारण बताने से पहले महसूस कर सकूंगा। वह वृत्ति ही एक ऐसी चीज़ है जिसे वाइब-कोडिंग प्रतिस्थापित नहीं कर सकती। एआई आपको विश्वसनीय दिखने वाला कोड सौंपता है। जब यह कचरा हो तो आपको एक नाक की आवश्यकता होती है।

दूसरा परिवर्तन सरल है: कोई भी कोड लिखे जाने से पहले, मैं डिज़ाइन का काम स्वयं हाथ से कर रहा हूं। कोई अस्पष्ट दस्तावेज़ नहीं. ठोस इंटरफ़ेस, संदेश प्रकार, स्वामित्व नियम। एआई जिन वास्तुशिल्प निर्णयों को गलत बनाता रहा, वे अब पहले संकेत से पहले लिखित रूप में किए जाते हैं। क्या यह पुनर्लेखन को अपने ही बोझ के नीचे ढहने से बचाने के लिए पर्याप्त है… मैं पता लगाऊंगा।

इस बीच, टीयूआई पर तारांकित करें और थोड़ा प्यार दिखाएं!

k10s जीथब

K10S.देव


[1] बबल टी एल्म आर्किटेक्चर पर आधारित गो के लिए एक टीयूआई ढांचा है। उत्कृष्ट है। K10s में वास्तुकला की समस्याएँ मेरी थीं, बबल टी की नहीं।

[2] “असंभव राज्यों को असंभव बनाना” एल्म/रस्ट समुदायों का एक वाक्यांश है। विचार: अपने प्रकारों को डिज़ाइन करें ताकि रनटाइम पर अमान्य राज्यों की जांच करने के बजाय अमान्य राज्यों का निर्माण न किया जा सके।

#एआई #आर्किटेक्चर #गो #कुबेरनेट्स #रस्ट #तुई #वाइब-कोडिंग



<a href

Leave a Comment