Changelog
Cosa c'è di nuovo.
Ogni miglioramento alla piattaforma, come si spedisce.
· kamo-shared-library
Bill l'intero termine davanti, e dire così sul preventivo
Un org che accusava di fronte ha visto il tasso mensile su un accordo di tre anni. The decidere che esisteva già. ma ha raggiunto solo QuoteConversionService, al momento della conversione. The documento stesso è sempre stato valutato come se il cliente pagato per periodo. applicareOptionHeadlineTotals ora rami sulla base: FULL CONTRACT prende contractTotal (che già trasporta la tassa di configurazione, quindi non deve essere aggiunto due volte, FIRST PERIOD PLUS SETUP mantiene la prima fattura. periodoTotale e mensileEquivalente sono intatti sotto entrambi — i tassi di risparmio si confrontano contro mensileEquivalente, e spostandolo cambierebbe ogni metro sulla pagina. La base è STAMPED sulla citazione quando viene sollevata piuttosto che risolta dal vivo. Una citazione indica i termini e un amministratore che capovolge l'interruttore org la prossima settimana deve non li ribadisce su un documento che un cliente già detiene. Leggere l'impostazione org al momento della conversione ha fatto esattamente questo: citazioni ri-prezzo che erano già accettato. La conversione ora legge il timbro proprio della citazione, e la revisione lo trasporta in modo da una revisione non può silenziosamente ri-base una negoziazione dal vivo. La colonna è nulla perché ogni riga esistente lo precede; null si risolve a FIRST PERIOD PLUS SETUP, che è quello che queste citazioni sono state inviate sotto, quindi nulla già nelle mani di un cliente cambia valore.
· DaemonService
Tick SecurityService e' in programma
Ogni cinque minuti. Un programma è impostato al minuto, quindi una zecca più fine non compra nulla un membro noterebbe e un collega uno fa un rapporto 8:00 arrivare alle 8:15. DaemonService possiede questo per lo stesso motivo che possiede il pacchetto legale e e-mail: repliche: 1 con strategia Recreate significa che il vecchio pod è andato prima dell'inizio del nuovo, quindi una finestra di distribuzione non può raddoppiare il fuoco. Un @Scheduled in SecurityService, che rotola con sbalzo, provabilmente può — e non c'è ShedLock ovunque in questa flotta, quindi il conteggio dei pod è l'intera garanzia. @DisallowConcurrentExecution perché una lunga spazzata non deve avere un secondo iniziato sopra di esso: entrambi avrebbero trovato gli stessi orari dovuti, dal prossimoRunAt è solo avanzato una volta che il primo li ha rivendicati, e lo stesso rapporto sarebbe uscito Due volte. Timeout espliciti per lo stesso motivo per cui i lavori di semplificazione li hanno — un default RestTemplate aspetta per sempre, e un filo pinned qui significa che la spazzata non corre mai di nuovo fino a quando il pod riparte, che è indistinguibile da "niente era dovuto". Niente su un rapporto attraversa questo processo: la spazzata ritorna conta, mai righe.
· SecurityService
Eseguire i rapporti che sono dovuti, e inviarli
Un endpoint interno, X-Internal-Auth gated e costante rispetto, che DaemonService ticks. Il cron rimane nel daemon perché è replica: 1 con strategia Ricreare, quindi una finestra di distribuzione non può raddoppiare il fuoco; un @Scheduled in un servizio che rotola con sovratensione provabilmente può, e non c'è ShedLock ovunque in questa flotta. Per un lavoro il cui output è le cifre di un'organizzazione e-mail a un elenco di indirizzi, "solitamente una volta" non è una proprietà. Ogni programma viene eseguito come OWNER, con i diritti ricomputati a tempo di esecuzione dal loro sovvenzioni immagazzinate — non come il demone, che non ha nessuno, e non come chiunque abbia modificato il programma. Quindi, richiamare l'accesso di qualcuno ferma i loro orari di produzione dei dati non dovrebbero più vedere alla prossima corsa, piuttosto che ogni volta che una sessione scade. Senza questo, un programma è un modo per avere il server di leggere i dati sul vostro per favore che non si poteva leggere voi stessi. nextRunAt è avanzato BEFORE consegna è tentato. Un crash mid-delivery allora costa un mancato invio piuttosto che un loop senza limiti di duplicati, e un settimanale mancato La relazione è un fallimento molto più economico dello stesso rapporto che arriva quaranta volte. Il file reso va come un allegato piuttosto che un collegamento, perché questi sono inviato regolarmente a un cliente o un contabile senza account. Un indirizzo cattivo fa non costa agli altri destinatari il loro rapporto, ma una corsa in cui ogni indirizzo è fallito registrato FAILED piuttosto che consegnato a nessuno. Un rapporto vuoto non viene inviato a meno che il proprietario lo ha chiesto: un'email settimanale che arriva sempre smette di essere letto.
· EmailService
Lasciate che un invio transazionale porti un allegato
Aggiunto per i rapporti programmati, che sono un PDF reso o cartella di lavoro che deve raggiungere le persone che potrebbero non avere account — un link non sarebbe una consegna per loro. Lo strato SMTP ha preso allegati da quando è stato scritto; questo percorso semplicemente superato null. Quindi nulla su come la posta è costruita cambia, il campo è facoltativo, e ogni esistente chiamante prende esattamente il percorso che ha sempre fatto. Base64 piuttosto che multipart perché questo endpoint è JSON server-to-server e ogni chiamante costruisce già un corpo JSON. Gli allegati sovradimensionati e malformati sono REFUSED piuttosto che troncato: una cartella di lavoro di 40MB rimbalzata costa il dominio di invio la reputazione, e quella silenziosamente troncata è un file che il destinatario non può aprire e non può spiegare. Il lancio è ciò che permette al chiamante di registrare una corsa fallita invece di un consegnato uno.
· kamo-shared-library
L'email di un rapporto programmato arriva in
RELAZION SCHEDULED, registrato in CANONICAL KEYS — assente da tale elenco un modello è un 404 permanente, qualsiasi file esistano accanto a esso. L'organismo stabilisce l'ambito dati. Un destinatario che non è il proprietario del programma non ha altro modo per dire un totale di organizzazione da uno che copre solo quello che il proprietario può vedere, e questi sono documenti diversi. Si chiama anche il proprietario due volte: una che ha stabilito il programma, e una volta come le cui cifre sono.
· SecurityService
Le liste di una finestra di dialogo di condivisione seleziona da, e un target di condivisione che si adatta a ogni id
E' il momento giusto. restituisce la gente dell'organizzazione, reparti e ruoli come opzioni picker, in una chiamata e in una forma. Costruito su i fornitori di opzioni di reporting piuttosto che sui membri, reparti e ruoli endpoint: ognuno risponde alla domanda di uno schermo diverso e ritorna un altro forma, e una finestra di dialogo di condivisione che consuma tutti e tre si romperà ogni volta che uno di loro cambiato per motivi non correlati alla segnalazione. Gated su SHARE REPORTS — l'elenco di tutti nell'organizzazione non è qualcosa da consegnare a un membro senza motivo per vederlo. Un obiettivo di condivisione è ora una stringa in tutto. Un membro id è un lungo 19 cifre e un dipartimento o ruolo id è un UUID; la colonna lunga che ha iniziato con potrebbe tenere metà i tipi, e la metà non avrebbe potuto fallire da accoppiamento silenzioso Nessuno. I controlli del dipartimento e del ruolo si confrontano come testo e risolvono con l'adesione ORA piuttosto che da quale sessione è capitato di tenere quando la quota è stata fatta. Tutti i tre assegni non sono chiusi. Una parte che non può essere valutato non concede.
· kamo-internal
Passo attraverso ogni colpo di ricerca, non solo il più recente
La ricerca nella parte superiore di una finestra di chat ha segnato ogni avvenimento della query e poi arrotolato a esattamente uno di loro — il più nuovo — con nulla dire quanti altri c'erano e non c'era modo di raggiungerli. Un membro che cerca una conversazione per una fattura il numero potrebbe vedere la parola ha evidenziato tre schermi in su e non aveva controllo che avrebbe Portateli. Ora si comporta come la barra di ricerca che assomiglia già: un contatore lettura "3/12", una freccia verso i messaggi più vecchi e uno verso il nuovo, entrare e Shift+ Inserisci per lo stesso due mosse, e il colpo è stato tirato in ambra solida contro il giallo del Riposa. L'atterraggio sul nuovo colpo è invariato, quindi un membro che ha mai digitato una query e leggere il risultato vede quello che hanno sempre fatto, ora con il conteggio accanto a esso. Due decisioni sotto valgono la pena di scrivere. I colpi sono contati per caso piuttosto che per messaggio, e contro le parole le bolle effettivamente disegnare — la traduzione in cui un messaggio ha uno, che la vecchia ricerca non l'ha fatto. Il contatore e i segni sono due punti di vista di un elenco prodotto da un singolo scansione (findOccurrences, condiviso con il evidenziatore), quindi "12" è sempre esattamente numero di segni sullo schermo. Due scansioni che non erano d'accordo anche leggermente lascerebbe frecce sbarcando sul nulla. paged: i messaggi arrivano in basso mentre una ricerca è aperta e le pagine più vecchie atterrano come "risultato 3" sarebbe tranquillamente diventare un messaggio diverso. Ne consegue che "1 di 3" al più vecchio colpo non significa che non c'è niente più vecchio — solo che nulla più vecchio è stato fetched — così facendo un passo indietro da lì chiede per la pagina precedente e atterra su che cosa esso porta, la stessa mossa che la lista già fa quando qualcuno scorre in alto. Si avvolge al più nuovo solo una volta che il server è esaurito, o dopo cinque pagine sono arrivati senza colpi affatto: una pressione di una freccia non deve essere in grado di camminare per anni conversazione al suo primo messaggio. La geometria di riposo della barra degli strumenti è intatta. Il contatore e le frecce appaiono con il query piuttosto che con il campo, perché costano circa sessanta pixel e un vuoto la casella di ricerca non ha niente da contare.
· kamo-shared-library
Un rapporto di servizio-lavoro, e un obiettivo di condivisione che si adatta a ogni id
ServiceWorkReport è il decimo rapporto seme e quello che fa portata a livello di riga reale piuttosto che semplicemente presente. Il commercio trasporta il proprio/tutte le coppie della piattaforma — VIEW OWN SERVICE WORK si trova accanto a VIEW SERVICE WORK come un fratello — e una visita porta assegnatoMemberId, quindi un tecnico che tiene solo la destra più stretta vede il loro lavoro mentre il loro supervisore vede il ramo. Una definizione, due risposte, e niente inventato per la segnalazione. Senza di esso SELF e TEAM sarebbe percorsi non testati nella parte di questo sistema è meno accettabile lasciare Non testato. Le righe sono visite, non lavori: un lavoro con tre visite è tre pezzi di lavoro, forse da tre persone in tre giorni, e crollare loro fa "chi ha fatto cosa, quando è impossibile. ReportViewShare.targetId diventa target key, una stringa. Un membro è un 19 cifre Lungo ma un dipartimento e un ruolo sono UUID, quindi una colonna INT8 potrebbe contenere metà tipo di condivisione — e la metà non avrebbe potuto fallire da corrispondenza silenziosa Nessuno. Rinominato piuttosto che ridisegnato perché l'aggiornamento dello schema di Hibernate aggiunge un colonna e non ridisegna una; la vecchia colonna non è mai stata scritta a. LeadPipelineConversionReport applica ora il pavimento non firmato della griglia di piombo. Senza di essa il rapporto è un secondo, non verificato modo per vedere il pool non firmato, e il suo gruppo conta non è d'accordo con /leads per nessun motivo visibile.
· kamo-internal
Pin le cartelle ben note nella parte superiore della barra laterale
Posta in arrivo, bozze, entrata, giunzione, cestino e archivio smettere di muoversi. tengono l'ordine nella parte superiore della cartella, e tutto ciò che il membro ha fatto siede sotto sotto una voce "Le mie cartelle", ancora liberamente riordinabile. Le righe pinned non sono registrate con dnd-kit affatto piuttosto che rifiutare il goccia: una riga che semplicemente decodifica per muoversi è ancora un bersaglio a goccia, così facendo cadere una cartella nella casella di posta animare l'intera lista e poi silenziosamente metterlo Indietro. Tengono la colonna di presa come spazio vuoto, così entrambi i gruppi condividono una sinistra bordo invece di leggere come due liste. Pinning è volutamente più stretto di SystemFolder, che corrisponde alla foglia a qualsiasi profondità così i propri clienti di un membro. Archivio disegna l'icona dell'archivio. Questo è chiamata giusta per un'icona e quella sbagliata per una posizione, così pinnedRank guarda radici solo — una cartella qualcuno depositato sotto il proprio genitore rimane loro a organizzare, come fanno le sottocartelle di una cartella pinned. Spam siede nella fessura di Junk. Il backend si normalizza con Junk ovunque scriva, quindi un elenco server "Spam" sta nominando la stessa cartella e non dovrebbe cadere tra il membro è proprio. Il grado vive nella cartellaTargets, che già possiede la cartella condivisa vocabolario, quindi il raccoglitore di movimento ordina le sue radici allo stesso modo e i due le superfici non possono allontanarsi di nuovo. applicareFolderOrder si muove a lib/email accanto esso: i globi del vitest non coprono app/hooks, quindi ordinare la logica lasciata lì è stato raccolto da nulla e passato non correndo mai. I membri che hanno trascinato la loro casella di posta prima di questo cambiamento lo chiamano ancora nel loro salvato preferenza. Quelle voci sono ignorate, e la prossima trascina le scrive.
· kamo-marketing
Ricostruire l'indice come mappa del prodotto
Era una lista ricorsiva di tutti e trecento articoli, rientrati dalla profondità. Tutto era sulla pagina e niente era rintracciabile, e la pagina pesava 3.9 MB. La forma viene presa in prestito dal prodotto. La porta anteriore di un cliente KamoCRM è Home launchpad -- una griglia di aree da cui scegliere -- quindi il centro di assistenza porta è lo stesso gesto, e qualcuno che arriva sa già come leggerlo. Le piastrelle sono una mappa di scala piuttosto che una griglia di carta. Ogni lista ciò che è in realtà all'interno con conteggi reali, quindi una tegola è alta come la sua zona è grande: Cliente Relations Manager ha otto sezioni e quarantanove articoli e lo guarda, il prossimo a Calendario con cinque. Le carte pari altezza con un'icona a pezzo sarebbero più tidi e rappresenterebbe il prodotto -- e sedici glifi sagomati differenziano 16 aree molto peggio di sedici tabelle di contenuti fanno. Aree che non hanno le sezioni elencano invece i loro articoli, quindi nessuna tegola dice solo il suo nome. La ricerca è l'azione primaria e ora si siede sopra tutto, classificato (un colpo a l'inizio di un titolo batte uno dopo, che batte uno nel corpo) e raggruppato per area, perché "che parte del prodotto è questo in" è la maggior parte della risposta. Le partite sono segnate in posizione. Una striscia di punto di partenza per un visitatore di prima volta, volutamente innumerata: la tre letti come una sequenza e non sono uno, dal momento che il terzo è di un amministratore la maggior parte dei membri non farà mai. I conti e le sopracciglia sono posizionati nella faccia mono già nel marchio, e la beige secondario -- che il sito aveva quasi smesso di usare -- trasporta il struttura. Che mantiene la pagina di essere verde monocromo senza introdurre tutto ciò che non è già KamoCRM. Le pagine della categoria e della sezione smettono anche di essere finiti: "In questa sezione" ora rende i collegamenti che il server non ha mai inviato, due-up con estratti e un contare dove una sezione ha uno.
· kamo-internal
Il Report Studio — una biblioteca e un renderer per ogni relazione
/reports è uno scaffale, non una griglia di carta: duecento scatole arrotondate identiche è un parete a scansione, e una riga dà una stanza di nome di un rapporto da leggere. nido di viste salvate sotto la relazione a cui appartengono, che è ciò che insegna la distinzione tutta sottosistema poggia su — non c'è nessun controllo di "Nuova relazione" ovunque, perché una relazione è una classe Java e ciò che un membro salva qui è una configurazione di uno. Lo studio è tre band: quello che stai chiedendo, una frase che dice quello che è su schermo e la risposta. La fascia media è portante. Una schermata di report non riesce quando qualcuno può vedere figure e non può dire ciò che sono figure di domanda in quella categoria — quante righe, durante quale periodo, ristretto come, e che copre quanto dell'organizzazione - si adatta in una frase. La nota data-scope ci siede verbatim, perché un totale prodotto sotto "solo i vostri dischi" è un documento diverso e nient'altro sullo schermo li distingue. Nulla in app/components/reports chiede quale relazione sta rendendo. visibileStudioControls legge le capacità dichiarate di una definizione e disegna le controlli corrispondenti; ParameterControl invia su un tipo di parametro; ogni cella rende attraverso il descrittore ReportFormat della sua colonna. Un rapporto di sola KPI ottiene un periodo e un pulsante Run e nient'altro, senza una linea di codice che lo conosce esiste — e il giorno che questo smette di essere vero, aggiungendo un rapporto smette di essere un Java cambiamento. formatters.ts è l'unico posto in cui un valore diventa una stringa, e il suo gemello Java legge lo stesso descrittore, quindi una figura sullo schermo e la figura in un PDF scaricato Non può essere disaccordo. Le date passano attraverso app/lib/serverTime.ts piuttosto che un secondo parser: Java scrive LocalDateTime senza offset e un browser legge che come Ora locale, che è ore fuori. La tavolozza del grafico è esagonale fisso, non derivato da --colore-primario. Tutti gli altri colore qui è una tinta del marchio dell'organizzazione, che è giusto per cromato e sbagliato per un grafico — una pale white-label primario darebbe una tavolozza di serie nessuno Potrebbe dirlo a parte. L'ordine è stato cercato e convalidato piuttosto che eyeballed: tutti gli otto passano la banda di leggerezza, pavimento cromato, separazione CVD, normale-visione controllo del pavimento e del contrasto sulla superficie leggera di questa applicazione, che è l'unica superficie E' cosi'. --report-* segue esattamente il modello --legale-* e --formazione-* e il suo sigillo è --accent-lavoro, che la tavolozza del pannello strumenti già nomina come l'accento per "tasks, calendar, report". I report si svolgono nel nav, nello specchio AppType, nelle bandiere dell'Organizzazione e l'albero del permesso. I suoi cinque diritti sono ingranti senza quell'ultimo.
· SecurityService
Eseguire, esportare e pianificare i rapporti del Report Studio
La metà del servizio della struttura: il catalogo, la corsa, i raccoglitori, quattro esportatori, opinioni salvate e orari. L'autorizzazione è esplicita su ogni percorso, perché questo servizio funziona permessoAll at la catena del filtro. Due diritti sono sempre in gioco — VIEW REPORTS apre il modulo, e il diritto di dominio del rapporto decide se tale rapporto può essere eseguito e come gran parte dell'organizzazione che copre. VIEW REPORTS da solo non ammette nessun dato, che è ciò che rende l'applicazione sicura per accendere un inquilino. ReportScopeResolver legge i diritti di fratelli OWN/ALL esistenti della piattaforma e niente inventato per la segnalazione. Un rapporto che nomina nessun diritto più stretto è a livello organizzativo e lo dice in una nota lo studio e ogni stampa di esportazione verbatim — un totale prodotto sotto "solo i vostri dischi" non è lo stesso documento come uno prodotto su tutta l'organizzazione, e un lettore non può dire altrimenti. sanitise() è il confine che mantiene una vista salvata dal diventare un utente definito relazione: una richiesta può solo scegliere tra quale definizione già dichiarata. Si'. strisce colonne sconosciute, livelli di raggruppamento e parametri, ma deliberatamente lascia una colonna sconosciuta in un FILTER per il motore di query da rifiutare — lasciando un nascosto la colonna cambia la presentazione, lasciando cadere un filtro cambia la risposta. Esportazioni leggere lo stesso ReportResult lo schermo è stato disegnato e formato attraverso lo stesso Descrittore ReportFormat, quindi una figura in un PDF e la cella da cui proviene Non può essere disaccordo. La cartella di lavoro scrive numeri come numeri con una maschera di formato piuttosto che come stringhe preformate, che è la differenza tra un foglio di calcolo e un CSV con un'estensione diversa. Il PDF porta l'organizzazione, il periodo, l'ambito dati, che ha eseguito e numeri di pagina. Un rapporto programmato viene eseguito come OWNER, con i diritti ricomputati in tempo di esecuzione, quindi richiamare l'accesso ferma un programma alla sua prossima corsa piuttosto che ogni volta che una sessione scade. prossimoRunAt avanza prima della consegna: non c'è ShedLock in questa flotta e repliche: 1 è tutto ciò che impedisce un doppio invio. pdfbox e poi-ooxml sono aggiunti QUI e non a kamo-shared-library, che ogni componenti di servizio-scans — altrimenti spedirebbero in ~60 immagini che mai rendi un rapporto. kamo.reporting.enabled è impostato in ConfigMap di questo servizio e in nessun altro luogo; questa proprietà è ciò che ferma un fagiolo del rapporto che viene costruito in servizi che non possono soddisfarlo. Rinomina anche una voce di stallo in ********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** ec2ae5e rinominato # TosyncLabels # linea di base, che ha fallito il ratchet su origine/principe in entrambe le direzioni a una volta. Il gestore risolve un chiamante — getOrganizationFromRequest, 401 quando null — quindi questo è un rinominare, non un allargamento.
· kamo-shared-library
Il quadro di Report Studio e nove rapporti costruiti su di esso
Un rapporto è ora una classe Java qui e niente altrove. ReportDefinition descrive che cosa è un rapporto, che cosa può essere chiesto, quali colonne ha e come ciascuno dovrebbe essere letto; il frontend rende qualsiasi cosa che dichiara, in modo da aggiungere un report non ha bisogno di endpoint, nessuna pagina e nessun TypeScript. I pezzi che portano il peso: - ReportCapability è il motivo per cui il frontend può essere generico. Un rapporto che dichiara solo FAVORITE e SCHEDULE non ottiene nessun costruttore di filtri, nessun gestore di colonne e nessun pannello di gruppo — ManagementOverviewReport esiste in parte come il canario per quello. - ReportFormat è un descrittore, mai una stringa resa, quindi una moneta nella tabella, il grafico tooltip, il CSV e il PDF non possono essere in disaccordo. - RapportoSicurezza Scope proviene dai diritti di dominio OWN di ogni rapporto, mai da un giusto. Un rapporto di vendita si allarga su VIEW ALL LEADS esattamente come il piombo Griglia, quindi la segnalazione non può mai diventare un modo per aggirare il modello di sicurezza. - ReportQuerySupport mette l'ambito predicato prima, rifiuta una colonna sconosciuta chiave invece di lasciarla, e si unisce a sinistra ogni associazione — un'unione implicita è un INNER, e le file con nulla dall'altra parte sono quelle Qualcuno sta cercando. - ReportAggregator mantiene un gruppo nullo come proprio gruppo, quindi i subtotali aggiungono ancora fino al grande totale. - Le date si risolvono nel fuso orario del MEMBER e sono a metà aperto. A 03:30 UTC su 1 settembre un membro a Los Angeles è ancora in agosto. Ogni classe di report trasporta @Component e @ConditionalOnProperty. Questo non è decorazione: ogni servizio scansiona com.kamo ma dichiara il proprio esplicito @EnableJpaRepositories elenco, così un nudo @Component bisogno di un EntityManager è costruito in tutti loro e CrashLoops quelli che non possono soddisfarlo. The la proprietà è impostata solo in SecurityService, e ReportBeansAreConditional Il test fallisce la costruzione se un rapporto lo dimentica. Reports è un app (ServiceType id 32) con cinque diritti (295-299). VIEW REPORTS è la porta del modulo e non concede alcun dato da solo. I nomi VIEW REPORTS e EXPORT REPORTI esistevano a ids 1 e 2 fino al 2026-08-14, quando furono rimossi insieme alle pagine acrobatiche dietro di loro; i diritti persistono come getId(), quindi nessuna concessione fatto prima allora può risolvere a questi. PhiModule.REPORTING è PERMITTED — il motore legge in-cluster e non memorizza copia. L'esportazione e la programmazione lasciano il confine e sono governati dal loro diritti propri e da EMAIL.
· kamo-internal
L'importatore e tre cose che ci ha insegnato
Pubblica il corpus guidando la vera API KBService piuttosto che scrivere righe -- un INSERT diretto salta la riga di versione, il kb.article. evento pubblicato che è l'unica cosa indicizzare un articolo per l'Assistente AI, e la traduzione fan-out. Un'importazione che sembrava successo e ha lasciato "Train AI Con Questo articolo" non fa nulla su trecento articoli è il tipo di fallimento Nessuno se ne accorge per un mese. Tre difetti che ha superficie, tutti fissati qui: 19-digit ids non deve mai toccare un numero JavaScript. Numero('1168485648209608710') è 1168485648209608700. Una sessione coniata con quell'org id autentica perfettamente e si risolve a un'organizzazione che non esiste -- la panoramica è tornato vuoto e letto come "la base di conoscenza è vuota". La stessa trappola seconda volta sull'articolo uid, dove reorder ha risposto 403 perché il genitore Id era stato arrotondato e non corrispondeva nulla. Gli id di sessione sono ora scritti nel JSON come letterali grezzi, e gli id dell'articolo sono letti dal testo di risposta prima JSON.parse può toccarli. Il genitore nullo di una categoria è la sezione, non la radice. Inviare null avrebbe pubblicato sedici articoli di alto livello, e il sito di marketing -- che chiede l'albero sotto help-center -- avrebbe mostrato una pagina vuota. kubectl port-forward non sopravvive a poche migliaia di richieste. Trasporti guasti ora riprovare con backoff; errori HTTP non, perché ripetere un 403 quattro volte richiede solo più tempo per fallire. L'autore è Sage, che già autori l'articolo Centro assistenza. Un articolo autore e ultimo editore provengono dalla sessione che lo ha scritto e sono mostrati da nome su ogni articolo nella console.
· kamo-shared-library
Persistere una regola di citazione-contatto del mercato, e rifiutare una mezza quota
I DTO hanno portato citazioneContactMode e citazioneContactMemberId ma nessun scrittore letto loro, così l'impostazione arrotondata attraverso la forma e è stato silenziosamente caduto. Sia i percorsi di creazione che di aggiornamento lo applicano ora. Il membro è tenuto solo sotto l'ESPLICIT e sgomberato altrimenti, quindi un mercato che interruttori indietro non lascia una persona dietro per essere ripristinata da un capo successivo, ed EXPLICIT nominare nessuno è respinto piuttosto che tranquillamente degradato — il versione tranquilla stampa un nome diverso sulla citazione successiva di chi ha salvato il forma prevista. La ricerca è org-scoped: gli ids sono globalmente unici, quindi un uno squisito avrebbe lasciato un inquilino il nome del personale di un altro inquilino sul proprio documento.
· kamo-internal
Scegliere chi un nome di preventivo come il contatto del cliente
La finestra di dialogo di mercato guadagna "Quote's Future Contatto" sotto Abilita citazioni: il piombo agente assegnato, una scelta fatta per preventivo, o una persona esplicita scelto con il stesso agenteSelector il filtro /leads utilizza. Sotto Regolabile Per Quote l'editor cresce una sezione Contatto, semenzata dall'istantanea che il server ha preso quando il preventivo è stato sollevato — che è l'agente del piombo, quindi si apre sulla persona già lì — e salva il cambiamento con il resto della citazione. Le tre decisioni lato cliente si sono spostate da due grandi componenti in app/lib/quote/quoteContact.ts. Sono stati chiesti in entrambi gli schermi e gli schermi il dissenso è invisibile fino a quando un documento stampa la persona sbagliata; come un modulo sono anche testabili, che in linea ternari in una finestra di dialogo 1300-linea non erano. L'anteprima mostra un contatto appena scelto per nome con il titolo, il telefono e l'e-mail vuoto: questi sono risolti lato server dalla casella di posta e dall'estensione di quel membro, quindi portare il numero della persona precedente sotto un nuovo nome sarebbe una bugia banner non salvato cambia non giustifica.
· SecurityService
Onora la regola di contatto di un mercato ovunque una citazione nomi qualcuno
Il dettaglio del personale, l'anteprima del personale e la pagina del cliente ora risolvono la contatto attraverso un metodo, in modo da non poter essere in disaccordo su chi è stampato stesso documento. Sotto ASSIGNED AGENT che è l'agente del piombo risolto per lettura, come prima; sotto le altre due modalità è l'istantanea che la citazione porta, con l'agente del piombo come il fallback per una citazione sollevata prima che l'impostazione esistesse o uno che nomina qualcuno che da allora è partito. Creare francobolli l'istantanea dal mercato piuttosto che dal corpo di richiesta, e l'aggiornamento applica un nuovo contatto solo quando il mercato esegue ADJUSTABLE PER QUOTE e il membro appartiene all'org — un mercato che nomina un contatto esplicito significa che, e un PATCH fatto a mano è come tale promessa sarebbe altrimenti rotto. I cinque gestori di QuoteController hanno costruito il cliente/agente/regolabile separatamente; ora condividono uno staffView, che è ciò che mantiene i tre coerente.
· kamo-internal
L'articolo corpus, e la pipeline che lo costruisce
307 articoli su 16 categorie e 29 sezioni, dal progetto 248 articoli sotto Documentazione/ plus 59 qui scritti: ogni categoria e sezione pagina di atterraggio, un set Iniziato che copre l'imbarco bozze mai raggiunte, una categoria Piattaforma la cartella di origine ha avuto solo un README per, e i due articoli di creazione membro che erano semplicemente mancanti. Quattro pezzi: lib/markdown-to-lexical Segnala alla forma la base di conoscenza memorizza. Il nodo set è una whitelist di ciò che entrambi i renderrs già maneggiano, perché il rendering pubblico disegna un nodo sconosciuto come un div nudo -- nessun errore, no monito, un paragrafo che ha tranquillamente perso il suo stile su una pagina nessuno è Guarda. Le liste erano la parte difficile: una linea vuota non deve terminare una (la le bozze hanno lacune di screenshot tra i passi, e la fine lì riavvia i numerazione a 1) e un elemento rientrante è una sottolista, non una frase aggiunta al passo sopra di esso. lib/clean-source Quale progetto deve smettere di dire prima che venga pubblicato. Le costanti di permissione diventano le parole sullo schermo; la denominazione dei genitori un file .tsx o un campo entità andare interamente; la casella di controllo recensione e e le referenze trasversali sono ridimensionate piuttosto che cancellato, quindi "vedere Come posso visualizzare la mia scheda temporale?" punti all'articolo pubblicato il nome invece di uno che non esiste più. lib/titles Question titoli a quelli pubblicati. Come prendere una regola; il 53 i concetti sono chiamati a mano, perché una regola che potrebbe girare "Qual è il differenza tra Status e Sub-Status?" in un nome di pagina avrebbe più eccezioni rispetto ai casi. I liquami sono controllati per collisioni attraverso l'intero albero prima di un'importazione corre -- il database li costringe per organizzazione, non per genitore. costruzioni-relazioni Il grafico see-also, costruito indiretto perché i collegamenti sono simmetrico nel prodotto. 594 collegamenti, da 3 a 6 per articolo. A partire dal progetti propri correlati: materia anteriore, che qualcuno ha scelto durante la scrittura; in cima alla posizione nell'albero. La somiglianza di testo classifica i candidati ma mai si qualifica uno da solo -- "Note" e "Come Ricercare e Filtro Leads" condividono ogni parola sulla ricerca e non hanno nulla a che fare con ogni altri. Nessuna pagina è relativa al proprio genitore o bambino: una sezione già elenca i suoi articoli, e un articolo già ha pangrattato. 57 test, e la lista di vitest include cresce una linea in modo che effettivamente eseguire.
· kamo-shared-library
Un mercato decide chi nomina un preventivo come contatto del cliente
QuoteContactMode dà una LeadMarket tre risposte: l'agente assegnato al piombo (risolto per lettura, che è ciò che ogni citazione fa oggi e che cosa una colonna nulla significa), una persona esplicita, o una scelta fatta su ogni citazione. Questi ultimi due sono istantanee su Quote.contactMemberId quando il preventivo viene sollevato, in modo da cambiare un la mente del mercato non riscrive mai il contatto che un cliente era già dato, e un la revisione porta avanti l'istantanea — è la stessa offerta. QuoteService prende il contatto risolto come argomento piuttosto che leggerlo fuori dal corpo di richiesta: questo modulo raggiunge il piombo solo da id morbido, e un valore un cliente potrebbe impostare avrebbe permesso a chiunque essere nominato su un documento. Aggiorna gli onori solo quando il chiamante passa uno, ed è così che ADJUSTABLE PER QUOTE viene applicato lato server piuttosto che dall'editor nascondendo un campo. agenteForMember è chiamato piuttosto che sovraccarica — agenteFor(null, ...) sarebbe ambiguo in qualsiasi luogo di chiamata che passa una nullità letterale — e la forma di guida Ora le deleghe, così i due non possono derivare su un documento stampato.
· InitializerService
Colonne per il contatto preventivo di un mercato e l'istantanea per-quota
Un mercato nomina la sua risposta in QUOTE CONTACT MODE (+ QUOTE CONTACT MEMBER ID quando che la risposta è una persona esplicita), e ogni citazione mantiene il contatto che era E' una cosa da fare. Sia nullable senza default: una modalità null legge come l'agente assegnato e un'istantanea nulla torna ad esso, quindi ogni mercato esistente e citazione è invariata fino a quando qualcuno sceglie il contrario.
· kamo-internal
Curare gli articoli correlati di un articolo
KbRelated Pannello è un componente in tre posti: cura nel compositore e l'editor, e legge sulla vista dell'articolo. Aggiungi un articolo qui aggiunge anche questo a quell'articolo, e il pannello dice così -- altrimenti il seconda metà della scrittura sembra qualcosa che modifica un articolo non dovrebbe hanno fatto. Due modalità, perché i collegamenti possono essere scelti prima che l'articolo esista. Con una guida il pannello possiede il suo stato e scrive ogni cambiamento dritto attraverso: un riordino Persi a un dimenticato Save è un riordino che nessuno fa due volte. Senza uno, il compositore detiene la selezione e PUTs dopo creare, come una chiamata separata per lo stesso motivo per la visualizzazione è uno -- un link che non riesce a salvare dovrebbe lasciare l'articolo scritto dietro piuttosto che prenderlo giù con esso. Suggerimenti provengono dal server, che classifica fratelli davanti agli articoli che condividere semplicemente il vocabolario e filtrarli attraverso lo stesso scopo del lettore di qualsiasi altro letto. Il pannello mantiene la risposta del server piuttosto che l'elenco che ha inviato, così una guida che il server ha lasciato cadere non è mostrato come salvato.
· KBService
Articoli correlati, e un percorso pubblico che mantiene l'albero
Tre cose, tutti alla stessa pagina. Articoli correlati. KbArticoloRelationService possiede la simmetrica "vedere anche" link: elenco, set, suggerire e un chiaro che corre su soft delete in modo da un pensionato l'articolo non sofferma nella lista di qualcun altro e lo accorcia silenziosamente. Leggi passare attraverso KbReaderScope -- una lista di relazione non filtrata nomi i titoli di esattamente gli articoli le bandiere del pubblico si nascondono, che è l'oracolo leggibile() risponde 404 per evitare, arrivando attraverso una porta diversa. Il pubblico leggere è ancora più stretto: pubblicato e visibile solo al pubblico, quindi una pagina di marketing non può collegare un visitatore in materiale solo membro. Percorso pubblico. KbPublicArticoloDTO ora porta il percorso e gli antenati. il sentiero è catena slug BELOW la radice della sezione, quindi /help-center/help-center è non rappresentabile e un consumatore può costruire un collegamento nido invece di uno piatto. Questo non è cosmetico: la ricerca dell'articolo si risolve sull'ultimo segmento del percorso, quindi un il collegamento costruito dalla slug nudo rende ancora la pagina giusta e la persa la gerarchia non si presenta da nessuna parte finché qualcuno non legge la mappa del sito. Si legge invece di quattro. buildPublicTreeIndex sostituisce il separato buildUidToGuidMap / buildPublicChildrenMap scansioni -- una singola pagina articolo era fare tre letti full-table più un quarto per il suo genitore, tutte le proiezioni di la stessa lista. Breadcrumbs e relative carte ora traducono con l'articolo piuttosto che rimanere inglese sotto un corpo tradotto. 98 test, 30 di loro nuovi: forma del percorso e cicli in KbPublicPathTest, specchio manutenzione e filtraggio di portata in KbArticoloRelationServiceTest.
· InitializerService
Migrazione per KB ARTICLE RELATIONS
Specchi KbArticoloRelazione in kamo-shared-library. Scritto esplicitamente che da sinistra a ddl-auto in modo che i default UID e DATE CREATED vivano database, che è ciò che permette un semplice SQL inserire lavoro senza nominarli. L'indice unico è sulla coppia ordinata, non quella non ordinata: (A,B) e (B, A) sono entrambi previsti esistere, e costringere la coppia non ordinata sarebbe respingere la riga dello specchio il servizio scrive su ogni link. Già applicato alla produzione eseguendo queste dichiarazioni come l'app kamo utente; sono idempotent, quindi un'esecuzione KI completa successiva è un no-op.
· kamo-shared-library
Relazioni con l'articolo
KB ARTICLE RELATIONS detiene il curato "vedere anche" link tra gli articoli in una base di conoscenza. Le righe sono per direzione e la coppia è tenuta simmetrica da KbArticoloRelationService, che è l'unica cosa consentita di scriverli -- una coppia semi-scritta mostra il link su un articolo e non l'altro, ed è invisibile dal lato che ha ancora la sua fila. Due righe piuttosto che una, quindi ogni direzione porta il proprio SORT ORDER: un articolo ordina la lista sotto di esso senza disturbare l'altra estremità, e ogni leggere rimane una sola ricerca indicizzata su ARTICLE ID. Il DDL e' gia' atterrato. KamoInitializerService, applicato a Yugabyte come utente dell'app kamo).
· SecurityService
Pagina e ricerca la directory org, e fermare due costruttori di riga alla deriva
Le reti endpoints rispondono con l'intero insieme in un unico corpo. Questo va bene per un membro di una dozzina di org e impossibile per un operatore su una tenuta destinata a raggiungere per mostrare venti. Aggiunge GET /org/network-directory, che corrisponde, ordini e finestre nel database e restituisce una pagina. campo=tutto è gated su esattamente il controllo allnetworks si applica — SYSTEM USER attraverso PlatformRightsService — così tutte e tre le rotte a destra continuano a funzionare e l'elenco non può mai essere più ampio dell'azione dietro di esso. La stessa risposta torna come canEnterAsSystemMember, quindi un client costruisce le sue righe e i suoi pulsanti da una risposta. my-networks e all-networks avevano un costruttore di file scritto a mano ciascuno, e le copie avevano già alla deriva: all-networks mai emesso primarioColore, logoUrl o logoSimpleUrl, così /network ha disegnato ogni org un operatore non era un membro della console predefinita accento piuttosto che l'org. Nulla lo ha segnalato — le chiavi erano semplicemente assenti e la caduta del cliente ha fatto il resto. Tutti e tre ora condividono OrgNetworkRowBuilder.
· kamo-marketing
Starter base up $10 su ogni termine
69 / 59 / 49, da 59 / 49 / 39. La scala di impegno è intatta dal disegno — ancora esattamente $10 off per un anno e $20 per tre — che è lo standard ogni superficie ora afferma in dollari piuttosto che come percentuale. Fallbacks in cinque file si muovono con esso, e check-prezzo-tokens ritira il figure superate in modo che nessuno incolla un vecchio prezzo Starter indietro in un confronto pagina. Le tre descrizioni dei metadati citate "$39/seat/mo" come a partire dal prezzo; numero un risultato di ricerca mostra è un reclamo di prezzo come qualsiasi altro. Il commento in bundle a PricingClient mantiene le sue cifre $53/$59 ma è ora datato: sono la ragione di una decisione passata, non una citazione dal vivo, e una stala non datata prezzo in un commento legge come un bug alla persona successiva. Verificato contro il catalogo dal vivo piuttosto che i fallback: il triennio di Starter i render del sedile erano $49 / Ora $32.83, e la striscia di homepage concorda con /pricing.
· kamo-internal
L'offerta in esecuzione, su Plans & fatturazione e sul launchpad
La console citò i prezzi della lista mentre kamocrm.com pubblicizzò un terzo fuori dello stesso piano, perché nulla in kamo-internal poteva vedere che esisteva un'offerta. Ora legge l'offerta risolta da BillingService e mostra cosa fa al prezzo: un banner sopra il picker piano con un conto alla rovescia e che cosa salva sull'ordine in mano, e ogni carta di piano coperta ha colpito dal suo tasso impegnato al ne ha offerto uno. La stima sotto i prezzi già che la carta mostra, quindi i due Non può essere disaccordo. Il launchpad ottiene una pillola di una linea accanto alla pillola di prova — al suo LEFT, e mostrato se o meno la pillola di prova è in su, da un'organizzazione passato il suo processo senza l'abbonamento è esattamente a chi l'offerta è. E 'cancellato di essere in grado di agire su esso: un membro che può acquistare il proprio posto, o un proprietario che può acquistare l'organizzazione e chi non sta già pagando. L'ultimo test legge copertura.source, non copertura.paidBy — pagatoBy dice che l'OMS copre il sedile, e qualcuno seduto sull'abbonamento gratuito dell'organizzazione legge ORGANIZZAZIONE pur essendo su nessun piano pagato affatto. Offrendo un aggiornamento a qualcuno già pagare è il modo più veloce per rendere invisibile un banner. planOffer.ts detiene l'aritmetico in modo che la pillola e la console non possano derivare, e perché questi sono numeri che le persone spendono soldi su: la percentuale scende dal tasso per il termine COMITATO a, mai il mese a mese prezzo di listino, corrispondente Non e' vero. 18 test coprono l'ordine, il sedile e termine scaling, che un piano scoperto è lasciato solo, e che la data finale è la l'ultimo giorno il codice funziona piuttosto che il primo giorno non è. check-server-timestamps catturato la parsa di scadenza: `new Data(\`${d}T23:59Z\`)` è UTC esplicito e corretto, ma la guardia non può dirlo dal bug ingenuo-string esiste per catturare, e una guardia che le persone imparano a discutere è peggio del bug. Passa attraverso parseServerDate ora, che mantiene un valore di sola data a mezzanotte UTC — quindi la scadenza è quella più un giorno, che è anche più chiaro sul perché.
· BillingService
Servire l'offerta in esecuzione e dire se un sedile è realmente PAID per
Aggiunge GET /markets/{id}/promozione. Non c'era modo per una superficie di acquisto in-app conoscere un'offerta era in esecuzione a tutti — il sito di marketing leggere le promozioni attraverso catalogo pubblico proxy, e kamo-internal non aveva equivalente, quindi i Piani & fatturazione consolle quotato list prezzi mentre kamocrm.com pubblicizzò un terzo sullo stesso piano. restituisce l'offerta RESOLVED, non la fila cruda. Ogni consumatore della forma grezza ha dovuto ridistribuire le stesse tre cose — se è vivo oggi, quali piani copre per codice piuttosto che per UUID, e quali cicli — e tutti quelli che hanno fatto così ha ottenuto almeno un torto: il sito di marketing ha ampliato un codice a due piani in un codice ogni piano confrontando UUID contro i codici di piano, e il suo eroe filtrato su lo sconto TYPE e silenziosamente pubblicizzato nulla per mesi. La vita viene da AbbonamentoPromozioni.check, lo stesso calcolo predicatoPrezzo si applica quando esso in realtà sconti un ordine, quindi una superficie non può pubblicizzare un codice checkout sarà decoder. I piani ritornano in ordine di visualizzazione, perché le superfici li scrivono in un frase e "Business and Starter" legge come se i livelli corressero in discesa. /self/coverage ora restituisce anche `source`. `paidBy` nomi che coprono un sedile, che è una domanda diversa da se qualcosa è pagato per esso: un membro su l'abbonamento gratuito dell'organizzazione legge pagatoBy=ORGANIZZAZIONE ed è su nessun piano pagato. Tutto quello che offre un aggiornamento deve essere in grado di dire a quelli a parte, e pagatoBy non può.
· kamo-marketing
L'offerta raggiunge l'eroe e la striscia di prezzi
L'eroe aveva già un nastro di conto alla rovescia, e stava rendendo NONHING da allora il catalogo spostato ad una percentuale: `pickHeroOffer` filtrato su FLAT PER SEAT perché la copia del banner è stata "$18/seat ogni mese per 12 mesi". Un secondo, definizione più stretta di "che offerta è in esecuzione" era l'intero problema, quindi è andato — l'eroe, la pagina dei prezzi, la pagina dell'offerta, la calcolatrice e ora il homepage striscia tutti leggere l'unico `PagePromotion` risolvePromozione restituisce. Il nastro viene ri-chiamato alla percentuale, link a /pricing/offer, e conta fino alla fine dell'ultimo giorno (una notte ha funzionato un giorno prima). Porta il accento in legno l'offerta indossa su ogni altra superficie, quindi è riconoscibile come promozione prima di una parola di esso è letto; il suo inchiostro CTA è misurato contro quello gradiente piuttosto che sinistra bianca, che è sotto 4,5:1 a entrambe le estremità. La striscia di homepage ora rende lo stesso componente PlanPrice /pricing fa — era, ora, e ciò che si salva — invece della propria scala laminata a mano sopra annualePrezzo. Entrambi aperti su HOME TERM, chiamati una volta in plan-pricing.ts perché tre superfici erano di default indipendentemente, e i CTA della striscia portano il codice sui piani offrire coperture. Pianifica ids su una promozione risolta ora torna in ordine CATALOG. L'API ritorna loro in ordine di query — Affari prima di Starter — e l'eroe li scrive in un frase, in modo da leggere "su ogni sede di Business e Starter", come se i livelli in discesa. check-i18n-keys non poteva vedere Hero.tsx AT ALL: lega `const [t, slideshow] = Aspettare ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** `const t = ...`. Tre chiavi mancanti nel componente più letto del sito, invisibili alla guardia il cui intero scopo è catturare un percorso di chiave crudo che rende visibile testo. Ora comprende la forma destrutturata, e — più importante — un file che traduce chiaramente, ma non produce namespace è RELAZIONED piuttosto che saltato, così la nuova nuova forma vincolante costa un aggiornamento della guardia invece di spedizione "home.heroOfferCta" nell'eroe. La copertura è passata da 1044 chiavi a 1073. check-plan-pricing cresce a 55 controlli, coprendo come i piani di un'offerta sono scritti out e risolverePromozione contro la forma che l'API effettivamente restituisce — compreso che una promozione i cui piani non riescono a risolvere è rifiutata piuttosto che ampliata. Verificato contro il catalogo dal vivo piuttosto che il fallback: homepage e /pricing entrambi rendono $0 / $26.13 / $66.33 / Custom.
· kamo-marketing
Una linea di impegno a tre termini e una promozione che la pagina può dimostrare
Nuovi prezzi: Starter $59/$49/$39 e Business $119/$109/$99 al posto al mese su termini mensili, annuali e triennali. La ferrovia sostituisce la coppia mensile/annuale e la sua "Salva 18%" pillola — una percentuale era l'unità sbagliata per un appartamento $10/$20 per posto, quindi la pillola citata qualunque piano `find` è accaduto per tornare prima, e un toggle non può esprimere tre opzioni comunque. Il pollice è un lucchetto che si chiude come il termine allunga, che è la didascalia sopra di esso ("Lock in your rate today!") fatto letterale. La band di offerta afferma la promozione in esecuzione una volta, sopra le carte, e dice che è applicato automaticamente. Questo è vero piuttosto che decorativo: il piano CTA ora portare ?code=, che RegisterFlow già legge in sp checkout code, e solo su piani l'offerta copre effettivamente — inviando AUTUMN al checkout dal Libero o La scheda Enterprise prometterebbe uno sconto sul server poi decoder. Starter e biglietti da visita mostra era / Ora / che cosa si salva, e la promozione arriva dal tasso TERM, non il prezzo di listino — commettere prima, quindi prendere la percentuale di sconto il numero già riprodotto, che corrisponde a quello che è successo. Quattro superfici stavano ciascuno facendo questo aritmetico se stessi e disaccordo, quindi si sposta in app/lib/plan-pricing.ts e tutti leggono la stessa risposta. The Percentuale promo del calcolatore era in carica contro add-on, mailbox, estensioni e l'archiviazione così come i sedili, così la sua stima è venuto fuori sotto che cosa il server bollette; ora è indirizzato ai sedili del piano come quello del server. /pricing/offer è ricostruito intorno a qualsiasi promozione il catalogo porta. era "18DOFF / $18 off Business" hardcoded nel client E nella sua pagina fallback E attraverso nove stringhe di dizionario, e il catalogo si era spostato oltre tutti e tre. Inoltre: package-lock.json era fuori sincronizzazione con package.json (@swc/helpers mancante), Così `npm ci` — che è ciò che il Dockerfile corre — non è riuscito in linea principale. La costruzione dell'immagine era rotto indipendentemente da questo lavoro.

Il tuo universo ti sta aspettando.
Aggiungi i prodotti quando sei pronto: nessuna migrazione, nessun nuovo login, nessuna richiesta di pagamento.
Gratis per sempre — nessuna carta di credito richiesta